Skip to content

Commit 79c3e7a

Browse files
Merge pull request #17 from notion-dotnet/add-support-for-block-object-apis
Add support for Block API's 💖
2 parents 9d7967a + 40a7510 commit 79c3e7a

File tree

8 files changed

+403
-2
lines changed

8 files changed

+403
-2
lines changed

Src/Notion.Client/Api/ApiEndpoints.cs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,5 +14,11 @@ public static class UsersApiUrls
1414
public static string Retrieve(string userId) => $"/v1/users/{userId}";
1515
public static string List() => "/v1/users";
1616
}
17+
18+
public static class BlocksApiUrls
19+
{
20+
public static string RetrieveChildren(string blockId) => $"/v1/blocks/{blockId}/children";
21+
public static string AppendChildren(string blockId) => $"/v1/blocks/{blockId}/children";
22+
}
1723
}
1824
}
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
using System.Collections.Generic;
2+
3+
namespace Notion.Client
4+
{
5+
// TODO: need an input version of Block
6+
public interface IBlocksAppendChildrenBodyParameters
7+
{
8+
IEnumerable<BlockBase> Children { get; set; }
9+
}
10+
11+
public class BlocksAppendChildrenParameters : IBlocksAppendChildrenBodyParameters
12+
{
13+
public IEnumerable<BlockBase> Children { get; set; }
14+
}
15+
}
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.Threading.Tasks;
4+
using static Notion.Client.ApiEndpoints;
5+
6+
namespace Notion.Client
7+
{
8+
public interface IBlocksClient
9+
{
10+
Task<PaginatedList<BlockBase>> RetrieveChildrenAsync(string blockId, BlocksRetrieveChildrenParameters parameters = null);
11+
Task<BlockBase> AppendChildrenAsync(string blockId, BlocksAppendChildrenParameters parameters = null);
12+
}
13+
14+
public class BlocksClient : IBlocksClient
15+
{
16+
private readonly IRestClient _client;
17+
18+
public BlocksClient(IRestClient client)
19+
{
20+
_client = client;
21+
}
22+
23+
public async Task<PaginatedList<BlockBase>> RetrieveChildrenAsync(string blockId, BlocksRetrieveChildrenParameters parameters = null)
24+
{
25+
if (string.IsNullOrWhiteSpace(blockId))
26+
{
27+
throw new ArgumentNullException(nameof(blockId));
28+
}
29+
30+
var url = BlocksApiUrls.RetrieveChildren(blockId);
31+
32+
var queryParameters = (IBlocksRetrieveChildrenQueryParameters)parameters;
33+
34+
var queryParams = new Dictionary<string, string>()
35+
{
36+
{ "start_cursor", queryParameters?.StartCursor?.ToString() },
37+
{ "page_size", queryParameters?.PageSize?.ToString() }
38+
};
39+
40+
return await _client.GetAsync<PaginatedList<BlockBase>>(url, queryParams);
41+
}
42+
43+
public async Task<BlockBase> AppendChildrenAsync(string blockId, BlocksAppendChildrenParameters parameters = null)
44+
{
45+
if (string.IsNullOrWhiteSpace(blockId))
46+
{
47+
throw new ArgumentNullException(nameof(blockId));
48+
}
49+
50+
var url = BlocksApiUrls.AppendChildren(blockId);
51+
52+
var body = (IBlocksAppendChildrenBodyParameters)parameters;
53+
54+
return await _client.PatchAsync<BlockBase>(url, body);
55+
}
56+
}
57+
}
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
namespace Notion.Client
2+
{
3+
public interface IBlocksRetrieveChildrenQueryParameters : IPaginationParameters
4+
{
5+
}
6+
7+
public class BlocksRetrieveChildrenParameters : IBlocksRetrieveChildrenQueryParameters
8+
{
9+
public string StartCursor { get; set; }
10+
public string PageSize { get; set; }
11+
}
12+
}

Src/Notion.Client/Models/Block.cs

Lines changed: 205 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,205 @@
1+
using System.Collections.Generic;
2+
using System.Runtime.Serialization;
3+
using JsonSubTypes;
4+
using Newtonsoft.Json;
5+
using Newtonsoft.Json.Converters;
6+
7+
namespace Notion.Client
8+
{
9+
[JsonConverter(typeof(JsonSubtypes), "type")]
10+
[JsonSubtypes.KnownSubType(typeof(BulletedListItemBlock), BlockType.BulletedListItem)]
11+
[JsonSubtypes.KnownSubType(typeof(ChildPageBlock), BlockType.ChildPage)]
12+
[JsonSubtypes.KnownSubType(typeof(HeadingOneBlock), BlockType.Heading_1)]
13+
[JsonSubtypes.KnownSubType(typeof(HeadingTwoBlock), BlockType.Heading_2)]
14+
[JsonSubtypes.KnownSubType(typeof(HeadingThreeeBlock), BlockType.Heading_3)]
15+
[JsonSubtypes.KnownSubType(typeof(NumberedListItemBlock), BlockType.NumberedListItem)]
16+
[JsonSubtypes.KnownSubType(typeof(ParagraphBlock), BlockType.Paragraph)]
17+
[JsonSubtypes.KnownSubType(typeof(ToDoBlock), BlockType.ToDo)]
18+
[JsonSubtypes.KnownSubType(typeof(ToggleBlock), BlockType.Toggle)]
19+
[JsonSubtypes.KnownSubType(typeof(UnsupportedBlock), BlockType.Unsupported)]
20+
public class BlockBase
21+
{
22+
public string Object => "block";
23+
public string Id { get; set; }
24+
25+
[JsonConverter(typeof(StringEnumConverter))]
26+
public virtual BlockType Type { get; set; }
27+
28+
[JsonProperty("created_time")]
29+
public string CreatedTime { get; set; }
30+
31+
[JsonProperty("last_edited_time")]
32+
public string LastEditedTime { get; set; }
33+
34+
[JsonProperty("has_children")]
35+
public virtual bool HasChildren { get; set; }
36+
}
37+
38+
public class ParagraphBlock : BlockBase
39+
{
40+
public override BlockType Type => BlockType.Paragraph;
41+
42+
public ParagraphClass Paragraph { get; set; }
43+
44+
public class ParagraphClass
45+
{
46+
public IEnumerable<RichTextBase> Text { get; set; }
47+
public IEnumerable<BlockBase> Children { get; set; }
48+
}
49+
}
50+
51+
public class HeadingOneBlock : BlockBase
52+
{
53+
public override BlockType Type => BlockType.Heading_1;
54+
55+
[JsonProperty("heading_1")]
56+
public HeadingOneClass Heading_1 { get; set; }
57+
58+
public override bool HasChildren => false;
59+
60+
public class HeadingOneClass
61+
{
62+
public IEnumerable<RichTextBase> Text { get; set; }
63+
}
64+
}
65+
66+
public class HeadingTwoBlock : BlockBase
67+
{
68+
public override BlockType Type => BlockType.Heading_2;
69+
70+
[JsonProperty("heading_2")]
71+
public HeadingTwoClass Heading_2 { get; set; }
72+
73+
public override bool HasChildren => false;
74+
75+
public class HeadingTwoClass
76+
{
77+
public IEnumerable<RichTextBase> Text { get; set; }
78+
}
79+
}
80+
81+
public class HeadingThreeeBlock : BlockBase
82+
{
83+
public override BlockType Type => BlockType.Heading_3;
84+
85+
[JsonProperty("heading_3")]
86+
public HeadingThreeClass Heading_3 { get; set; }
87+
88+
public override bool HasChildren => false;
89+
90+
public class HeadingThreeClass
91+
{
92+
public IEnumerable<RichTextBase> Text { get; set; }
93+
}
94+
}
95+
96+
public class BulletedListItemBlock : BlockBase
97+
{
98+
public override BlockType Type => BlockType.BulletedListItem;
99+
100+
[JsonProperty("bulleted_list_item")]
101+
public BulletedListItemClass BulletedListItem { get; set; }
102+
103+
public class BulletedListItemClass
104+
{
105+
public IEnumerable<RichTextBase> Text { get; set; }
106+
public IEnumerable<BlockBase> Children { get; set; }
107+
}
108+
}
109+
110+
public class NumberedListItemBlock : BlockBase
111+
{
112+
public override BlockType Type => BlockType.NumberedListItem;
113+
114+
[JsonProperty("numbered_list_item")]
115+
public NumberedListItemClass NumberedListItem { get; set; }
116+
117+
public class NumberedListItemClass
118+
{
119+
public IEnumerable<RichTextBase> Text { get; set; }
120+
public IEnumerable<BlockBase> Children { get; set; }
121+
}
122+
}
123+
124+
public class ToDoBlock : BlockBase
125+
{
126+
public override BlockType Type => BlockType.ToDo;
127+
128+
[JsonProperty("to_do")]
129+
public ToDoClass ToDo { get; set; }
130+
131+
public class ToDoClass
132+
{
133+
public IEnumerable<RichTextBase> Text { get; set; }
134+
135+
[JsonProperty("checked")]
136+
public bool IsChecked { get; set; }
137+
138+
public IEnumerable<BlockBase> Children { get; set; }
139+
}
140+
}
141+
142+
public class ToggleBlock : BlockBase
143+
{
144+
public override BlockType Type => BlockType.Toggle;
145+
146+
public ToggleClass Toggle { get; set; }
147+
148+
public class ToggleClass
149+
{
150+
public IEnumerable<RichTextBase> Text { get; set; }
151+
public IEnumerable<BlockBase> Children { get; set; }
152+
}
153+
}
154+
155+
public class ChildPageBlock : BlockBase
156+
{
157+
public override BlockType Type => BlockType.ChildPage;
158+
159+
[JsonProperty("child_page")]
160+
public ChildPageClass ChildPage { get; set; }
161+
162+
public class ChildPageClass
163+
{
164+
public string Title { get; set; }
165+
}
166+
}
167+
168+
public class UnsupportedBlock : BlockBase
169+
{
170+
public override BlockType Type => BlockType.Unsupported;
171+
}
172+
173+
public enum BlockType
174+
{
175+
[EnumMember(Value = "paragraph")]
176+
Paragraph,
177+
178+
[EnumMember(Value = "heading_1")]
179+
Heading_1,
180+
181+
[EnumMember(Value = "heading_2")]
182+
Heading_2,
183+
184+
[EnumMember(Value = "heading_3")]
185+
Heading_3,
186+
187+
[EnumMember(Value = "bulleted_list_item")]
188+
BulletedListItem,
189+
190+
[EnumMember(Value = "numbered_list_item")]
191+
NumberedListItem,
192+
193+
[EnumMember(Value = "to_do")]
194+
ToDo,
195+
196+
[EnumMember(Value = "toggle")]
197+
Toggle,
198+
199+
[EnumMember(Value = "child_page")]
200+
ChildPage,
201+
202+
[EnumMember(Value = "unsupported")]
203+
Unsupported
204+
}
205+
}

Src/Notion.Client/Models/PaginatedList.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ public interface IPaginationParameters
1111

1212
public class PaginatedList<T>
1313
{
14-
public const string Object = "List";
14+
public const string Object = "list";
1515

1616
public List<T> Results { get; set; }
1717

Src/Notion.Client/RestClient.cs

Lines changed: 42 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
using System.Threading;
77
using System.Threading.Tasks;
88
using Newtonsoft.Json;
9+
using Newtonsoft.Json.Serialization;
910
using Notion.Client.Extensions;
1011
using Notion.Client.http;
1112

@@ -27,6 +28,14 @@ Task<T> PostAsync<T>(
2728
IDictionary<string, string> headers = null,
2829
JsonSerializerSettings serializerSettings = null,
2930
CancellationToken cancellationToken = default);
31+
32+
Task<T> PatchAsync<T>(
33+
string uri,
34+
object body,
35+
IDictionary<string, string> queryParams = null,
36+
IDictionary<string, string> headers = null,
37+
JsonSerializerSettings serializerSettings = null,
38+
CancellationToken cancellationToken = default);
3039
}
3140

3241
public class RestClient : IRestClient
@@ -36,7 +45,11 @@ public class RestClient : IRestClient
3645

3746
private readonly JsonSerializerSettings defaultSerializerSettings = new JsonSerializerSettings
3847
{
39-
NullValueHandling = NullValueHandling.Ignore
48+
NullValueHandling = NullValueHandling.Ignore,
49+
ContractResolver = new DefaultContractResolver
50+
{
51+
NamingStrategy = new CamelCaseNamingStrategy()
52+
}
4053
};
4154

4255
public RestClient(ClientOptions options)
@@ -139,6 +152,34 @@ void AttachContent(HttpRequestMessage httpRequest)
139152
throw new NotionApiException(response.StatusCode, message);
140153
}
141154

155+
public async Task<T> PatchAsync<T>(string uri, object body, IDictionary<string, string> queryParams = null, IDictionary<string, string> headers = null, JsonSerializerSettings serializerSettings = null, CancellationToken cancellationToken = default)
156+
{
157+
EnsureHttpClient();
158+
159+
void AttachContent(HttpRequestMessage httpRequest)
160+
{
161+
var serializedBody = JsonConvert.SerializeObject(body, defaultSerializerSettings);
162+
httpRequest.Content = new StringContent(serializedBody, Encoding.UTF8, "application/json");
163+
}
164+
165+
string requestUri = queryParams == null ? uri : QueryHelpers.AddQueryString(uri, queryParams);
166+
167+
var response = await SendAsync(requestUri, new HttpMethod("PATCH"), headers, AttachContent, cancellationToken: cancellationToken);
168+
169+
if (response.IsSuccessStatusCode)
170+
{
171+
return await response.ParseStreamAsync<T>(serializerSettings);
172+
}
173+
174+
var message = !string.IsNullOrWhiteSpace(response.ReasonPhrase)
175+
? response.ReasonPhrase
176+
: await response.Content.ReadAsStringAsync();
177+
178+
var errorMessage = await response.Content.ReadAsStringAsync();
179+
180+
throw new NotionApiException(response.StatusCode, message);
181+
}
182+
142183
private HttpClient EnsureHttpClient()
143184
{
144185
if (_httpClient == null)

0 commit comments

Comments
 (0)