diff --git a/Kontent.Ai.Management.Tests/Data/CustomApp/CustomApp.json b/Kontent.Ai.Management.Tests/Data/CustomApp/CustomApp.json new file mode 100644 index 00000000..1d49580b --- /dev/null +++ b/Kontent.Ai.Management.Tests/Data/CustomApp/CustomApp.json @@ -0,0 +1,15 @@ +{ + "id": "907623af-edeb-4096-8bb1-b24e1c476888", + "name": "Custom App", + "codename": "custom-app", + "source_url": "https://customapplication.net", + "config": "{ \"enabled\": \"True\" }", + "allowed_roles": [ + { + "id": "051e7717-0b51-4bfa-a180-542718ae6829" + }, + { + "id": "3c6cec4f-6f01-4eed-b5f4-7b2e7c8367e8" + } + ] +} \ No newline at end of file diff --git a/Kontent.Ai.Management.Tests/Data/CustomApp/CustomAppsPage1.json b/Kontent.Ai.Management.Tests/Data/CustomApp/CustomAppsPage1.json new file mode 100644 index 00000000..7e1bb5c4 --- /dev/null +++ b/Kontent.Ai.Management.Tests/Data/CustomApp/CustomAppsPage1.json @@ -0,0 +1,53 @@ +{ + "custom_apps": [ + { + "id": "907623af-edeb-4096-8bb1-b24e1c476888", + "name": "Custom App", + "codename": "custom-app", + "source_url": "https://customapplication.net", + "config": "{ \"enabled\": \"True\" }", + "allowed_roles": [ + { + "id": "051e7717-0b51-4bfa-a180-542718ae6829" + }, + { + "id": "3c6cec4f-6f01-4eed-b5f4-7b2e7c8367e8" + } + ] + }, + { + "id": "3c52d8e9-b21a-4fd2-98c5-dc76b238617a", + "name": "Advanced App", + "codename": "advanced-app", + "source_url": "https://advancedapplication.com", + "config": "{ \"enabled\": \"True\", \"version\": \"1.2\" }", + "allowed_roles": [ + { + "id": "17f8d60e-c437-4fcd-b4f3-f99508ff5de3" + }, + { + "id": "5d3a36c0-b3e6-470d-9a1f-8714303a2850" + } + ] + }, + { + "id": "f7a8c6c2-b914-44f7-bb74-9b5d6d4b7b9f", + "name": "Pro App", + "codename": "pro-app", + "source_url": "https://proapplication.org", + "config": "{ \"enabled\": \"True\", \"max_users\": \"50\" }", + "allowed_roles": [ + { + "id": "29b9b01e-95d9-4ca5-a0d1-d764ee65d019" + }, + { + "id": "9b9f4b7c-1b5d-4007-a8b3-d0cba7e88f98" + } + ] + } + ], + "pagination": { + "continuation_token": "+RID:~...", + "next_page": "https://manage.kontent.ai/v2/projects//?continuationToken=%2bRID%3a~..." + } +} \ No newline at end of file diff --git a/Kontent.Ai.Management.Tests/Data/CustomApp/CustomAppsPage2.json b/Kontent.Ai.Management.Tests/Data/CustomApp/CustomAppsPage2.json new file mode 100644 index 00000000..a20ccd9c --- /dev/null +++ b/Kontent.Ai.Management.Tests/Data/CustomApp/CustomAppsPage2.json @@ -0,0 +1,53 @@ +{ + "custom_apps": [ + { + "id": "d392aec1-9f74-44a0-97a4-d0f19db20a8b", + "name": "Beta App", + "codename": "beta-app", + "source_url": "https://betaapplication.io", + "config": "{ \"enabled\": \"False\", \"feature_flag\": \"beta_test\" }", + "allowed_roles": [ + { + "id": "3e3c6d7b-fb6f-42ab-bf50-e2f84c88b093" + }, + { + "id": "2347aabb-7403-4f1a-8a92-8a0e9e426ff9" + } + ] + }, + { + "id": "eb5429c5-d9e3-4628-8e59-7285863c6330", + "name": "Custom App 2.0", + "codename": "custom-app-v2", + "source_url": "https://customapp2.0.com", + "config": "{ \"enabled\": \"True\", \"update_check\": \"True\" }", + "allowed_roles": [ + { + "id": "5d6c0f93-e6d3-488b-b73a-f0b52d56f927" + }, + { + "id": "f870be79-3b45-4f67-ae2a-2db7ca55dbe4" + } + ] + }, + { + "id": "d8756d4b-fd4f-462d-b18b-df7b2e7f8a99", + "name": "Mobile App", + "codename": "mobile-app", + "source_url": "https://mobileapplication.mobi", + "config": "{ \"enabled\": \"True\", \"platform\": \"mobile\" }", + "allowed_roles": [ + { + "id": "0f601fa5-4538-4f87-b973-c876db9a1498" + }, + { + "id": "affd2f57-0a5a-4c89-8c93-78bb24a1b29f" + } + ] + } + ], + "pagination": { + "continuation_token": "+RID:~...", + "next_page": "https://manage.kontent.ai/v2/projects//?continuationToken=%2bRID%3a~..." + } +} \ No newline at end of file diff --git a/Kontent.Ai.Management.Tests/Data/CustomApp/CustomAppsPage3.json b/Kontent.Ai.Management.Tests/Data/CustomApp/CustomAppsPage3.json new file mode 100644 index 00000000..8246946c --- /dev/null +++ b/Kontent.Ai.Management.Tests/Data/CustomApp/CustomAppsPage3.json @@ -0,0 +1,53 @@ +{ + "custom_apps": [ + { + "id": "bbad8934-f918-4de3-9297-b0f689df5015", + "name": "Enterprise App", + "codename": "enterprise-app", + "source_url": "https://enterpriseapplication.com", + "config": "{ \"enabled\": \"True\", \"enterprise_mode\": \"enabled\" }", + "allowed_roles": [ + { + "id": "4a1a0de2-b3d4-4f5d-82fd-f175a5b5e7f7" + }, + { + "id": "76d2e5a8-b2db-468d-b4c2-b5e6c229d453" + } + ] + }, + { + "id": "4a33f45c-1b13-4010-84cf-cc204cdbb768", + "name": "Test App", + "codename": "test-app", + "source_url": "https://testapplication.test", + "config": "{ \"enabled\": \"False\", \"test_mode\": \"active\" }", + "allowed_roles": [ + { + "id": "87a56a6b-09a7-4c9d-86c4-d5332b04596f" + }, + { + "id": "0638d1d9-7682-4840-a6b5-2831498fdaaf" + } + ] + }, + { + "id": "e1dcee56-5ed3-4f9a-957f-04522832b351", + "name": "Demo App", + "codename": "demo-app", + "source_url": "https://demoapplication.demo", + "config": "{ \"enabled\": \"True\", \"demo_mode\": \"active\" }", + "allowed_roles": [ + { + "id": "3b8f9fbe-bd16-4789-b591-5370591844d7" + }, + { + "id": "2f3991f1-5cfc-45b5-a1e5-8f94e8b92c6a" + } + ] + } + ], + "pagination": { + "continuation_token": null, + "next_page": null + } +} \ No newline at end of file diff --git a/Kontent.Ai.Management.Tests/Data/CustomApp/ModifyCustomApp_AddInto_ModifiesCustomApp.json b/Kontent.Ai.Management.Tests/Data/CustomApp/ModifyCustomApp_AddInto_ModifiesCustomApp.json new file mode 100644 index 00000000..9b2fbd17 --- /dev/null +++ b/Kontent.Ai.Management.Tests/Data/CustomApp/ModifyCustomApp_AddInto_ModifiesCustomApp.json @@ -0,0 +1,18 @@ +{ + "id": "907623af-edeb-4096-8bb1-b24e1c476888", + "name": "New Custom App Name", + "codename": "new_custom_app_codename", + "source_url": "https://customapplication.net", + "config": "{ \"enabled\": \"True\" }", + "allowed_roles": [ + { + "id": "051e7717-0b51-4bfa-a180-542718ae6829" + }, + { + "id": "f8f0b5cb-f5b7-42e8-af85-fbdab3ddfacf" + }, + { + "id": "2c009e0b-bd05-4119-a189-58ed52b6818a" + } + ] +} \ No newline at end of file diff --git a/Kontent.Ai.Management.Tests/Data/CustomApp/ModifyCustomApp_Remove_ModifiesCustomApp.json b/Kontent.Ai.Management.Tests/Data/CustomApp/ModifyCustomApp_Remove_ModifiesCustomApp.json new file mode 100644 index 00000000..a0e25c19 --- /dev/null +++ b/Kontent.Ai.Management.Tests/Data/CustomApp/ModifyCustomApp_Remove_ModifiesCustomApp.json @@ -0,0 +1,12 @@ +{ + "id": "907623af-edeb-4096-8bb1-b24e1c476888", + "name": "New Custom App Name", + "codename": "new_custom_app_codename", + "source_url": "https://customapplication.net", + "config": "{ \"enabled\": \"True\" }", + "allowed_roles": [ + { + "id": "f8f0b5cb-f5b7-42e8-af85-fbdab3ddfacf" + } + ] +} \ No newline at end of file diff --git a/Kontent.Ai.Management.Tests/Data/CustomApp/ModifyCustomApp_Replace_ModifiesCustomApp.json b/Kontent.Ai.Management.Tests/Data/CustomApp/ModifyCustomApp_Replace_ModifiesCustomApp.json new file mode 100644 index 00000000..38715ebf --- /dev/null +++ b/Kontent.Ai.Management.Tests/Data/CustomApp/ModifyCustomApp_Replace_ModifiesCustomApp.json @@ -0,0 +1,15 @@ +{ + "id": "907623af-edeb-4096-8bb1-b24e1c476888", + "name": "New Custom App Name", + "codename": "new_custom_app_codename", + "source_url": "https://customapplication.net", + "config": "{ \"enabled\": \"True\" }", + "allowed_roles": [ + { + "id": "051e7717-0b51-4bfa-a180-542718ae6829" + }, + { + "id": "f8f0b5cb-f5b7-42e8-af85-fbdab3ddfacf" + } + ] +} \ No newline at end of file diff --git a/Kontent.Ai.Management.Tests/Kontent.Ai.Management.Tests.csproj b/Kontent.Ai.Management.Tests/Kontent.Ai.Management.Tests.csproj index 7c20c8c1..69f14e56 100644 --- a/Kontent.Ai.Management.Tests/Kontent.Ai.Management.Tests.csproj +++ b/Kontent.Ai.Management.Tests/Kontent.Ai.Management.Tests.csproj @@ -72,6 +72,18 @@ Always + + Always + + + Always + + + Always + + + Always + diff --git a/Kontent.Ai.Management.Tests/ManagementClientTests/CustomAppTests.cs b/Kontent.Ai.Management.Tests/ManagementClientTests/CustomAppTests.cs new file mode 100644 index 00000000..060563a1 --- /dev/null +++ b/Kontent.Ai.Management.Tests/ManagementClientTests/CustomAppTests.cs @@ -0,0 +1,251 @@ +using FluentAssertions; +using Kontent.Ai.Management.Extensions; +using Kontent.Ai.Management.Models.CustomApps; +using Kontent.Ai.Management.Models.CustomApps.Patch; +using Kontent.Ai.Management.Models.Shared; +using Kontent.Ai.Management.Tests.Base; +using System; +using System.Net.Http; +using Xunit; +using static Kontent.Ai.Management.Tests.Base.Scenario; + +namespace Kontent.Ai.Management.Tests.ManagementClientTests; + +public class CustomAppTests : IClassFixture +{ + private static readonly string CustomAppBaseUrl = $"{Endpoint}/projects/{ENVIRONMENT_ID}/custom-apps"; + private readonly Scenario _scenario = new(folder: "CustomApp"); + + [Fact] + public async void ListCustomAppsAsync_WithContinuation_ListsContentTypes() + { + var client = _scenario + .WithResponses("CustomAppsPage1.json", "CustomAppsPage2.json", "CustomAppsPage3.json") + .CreateManagementClient(); + + var response = await client.ListCustomAppsAsync().GetAllAsync(); + + _scenario + .CreateExpectations() + .HttpMethod(HttpMethod.Get) + .ListingResponse(response) + .Url(CustomAppBaseUrl) + .Validate(); + } + + [Fact] + public async void CreateCustomApp_ModelIsNull_Throws() + { + var client = _scenario.CreateManagementClient(); + + await client.Invoking(x => x.CreateCustomAppAsync(null)).Should().ThrowAsync(); + } + + [Fact] + public async void CrateCustomApp_CreatesCustomApp() + { + var client = _scenario.WithResponses("CustomApp.json").CreateManagementClient(); + var expected = _scenario.GetExpectedResponse(); + var createModel = new CustomAppCreateModel + { + Name = expected.Name, + Codename = expected.Codename, + SourceUrl = expected.SourceUrl, + Config = expected.Config, + AllowedRoles = expected.AllowedRoles + }; + + var response = await client.CreateCustomAppAsync(createModel); + + _scenario.CreateExpectations() + .HttpMethod(HttpMethod.Post) + .RequestPayload(createModel) + .Response(response) + .Url(CustomAppBaseUrl) + .Validate(); + } + + [Fact] + public async void GetCustomApp_IdentifierIsNull_Throws() + { + var client = _scenario.CreateManagementClient(); + + await client.Invoking(x => x.GetCustomAppAsync(null)).Should().ThrowAsync(); + } + + [Fact] + public async void GetCustomApp_ById_GetsCustomApp() + { + var client = _scenario.WithResponses("CustomApp.json").CreateManagementClient(); + var identifier = Reference.ById(Guid.NewGuid()); + + var response = await client.GetCustomAppAsync(identifier); + + _scenario.CreateExpectations() + .HttpMethod(HttpMethod.Get) + .Response(response) + .Url(CustomAppBaseUrl + $"/{identifier.Id}") + .Validate(); + } + + [Fact] + public async void GetCustomApp_ByCodename_GetsCustomApp() + { + var client = _scenario.WithResponses("CustomApp.json").CreateManagementClient(); + var identifier = Reference.ByCodename("custom_app"); + + var response = await client.GetCustomAppAsync(identifier); + + _scenario.CreateExpectations() + .HttpMethod(HttpMethod.Get) + .Response(response) + .Url(CustomAppBaseUrl + $"/codename/{identifier.Codename}") + .Validate(); + } + + [Fact] + public async void ModifyCustomApp_AddInto_ModifiesCustomApp() + { + var client = _scenario.WithResponses("ModifyCustomApp_AddInto_ModifiesCustomApp.json").CreateManagementClient(); + var identifier = Reference.ById(Guid.NewGuid()); + var changes = new CustomAppAddIntoPatchModel[] + { + new() + { + PropertyName = PropertyName.AllowedRoles, + Value = new[] + { + Reference.ByCodename("new_allowed_role_codename") + } + } + }; + + var response = await client.ModifyCustomAppAsync(identifier, changes); + + _scenario.CreateExpectations() + .HttpMethod(HttpMethod.Patch) + .RequestPayload(changes) + .Response(response) + .Url(CustomAppBaseUrl + $"/{identifier.Id}") + .Validate(); + } + + + [Fact] + public async void ModifyCustomApp_Remove_ModifiesCustomApp() + { + var client = _scenario.WithResponses("ModifyCustomApp_Remove_ModifiesCustomApp.json").CreateManagementClient(); + var identifier = Reference.ById(Guid.NewGuid()); + var changes = new CustomAppRemovePatchModel[] + { + new() + { + PropertyName = PropertyName.AllowedRoles, + Value = new[] + { + Reference.ByCodename("allowed_role_codename") + } + } + }; + + var response = await client.ModifyCustomAppAsync(identifier, changes); + + _scenario.CreateExpectations() + .HttpMethod(HttpMethod.Patch) + .RequestPayload(changes) + .Response(response) + .Url(CustomAppBaseUrl + $"/{identifier.Id}") + .Validate(); + } + + [Fact] + public async void ModifyCustomApp_Replace_ModifiesCustomApp() + { + var client = _scenario.WithResponses("ModifyCustomApp_Replace_ModifiesCustomApp.json").CreateManagementClient(); + var identifier = Reference.ById(Guid.NewGuid()); + var changes = new CustomAppReplacePatchModel[] + { + new() { PropertyName = PropertyName.Name, Value = "New Custom App Name" }, + new() { PropertyName = PropertyName.Codename, Value = "new_custom_app_codename" }, + new() { PropertyName = PropertyName.SourceUrl, Value = "https://newcustomapplication.net" }, + new() { PropertyName = PropertyName.Config, Value = "{ \"enabled\": \"False\" }" }, + new() + { + PropertyName = PropertyName.AllowedRoles, + Value = new[] + { + Reference.ByCodename("allowed_role_codename"), + Reference.ById(new Guid("f8f0b5cb-f5b7-42e8-af85-fbdab3ddfacf")) + } + } + }; + + var response = await client.ModifyCustomAppAsync(identifier, changes); + + _scenario.CreateExpectations() + .HttpMethod(HttpMethod.Patch) + .RequestPayload(changes) + .Response(response) + .Url(CustomAppBaseUrl + $"/{identifier.Id}") + .Validate(); + } + + [Fact] + public async void ModifyCustomApp_IdentifierIsNull_Throws() + { + var client = _scenario.CreateManagementClient(); + var changes = new CustomAppReplacePatchModel[] + { + new() { PropertyName = PropertyName.Name, Value = "New space name" } + }; + + await client.Invoking(x => x.ModifyCustomAppAsync(null, changes)).Should().ThrowAsync(); + } + + [Fact] + public async void ModifyCustomApp_ChangesAreNull_Throws() + { + var client = _scenario.CreateManagementClient(); + var identifier = Reference.ById(Guid.NewGuid()); + + await client.Invoking(x => x.ModifyCustomAppAsync(identifier, null)).Should() + .ThrowAsync(); + } + + [Fact] + public async void DeleteCustomApp_ById_DeletesCustomApp() + { + var client = _scenario.CreateManagementClient(); + var identifier = Reference.ById(Guid.NewGuid()); + + await client.DeleteCustomAppAsync(identifier); + + _scenario + .CreateExpectations() + .Url(CustomAppBaseUrl + $"/{identifier.Id}") + .HttpMethod(HttpMethod.Delete) + .Validate(); + } + + [Fact] + public async void DeleteCustomApp_ByCodename_DeletesCustomApp() + { + var client = _scenario.CreateManagementClient(); + var identifier = Reference.ByCodename("custom_app"); + + await client.DeleteCustomAppAsync(identifier); + + _scenario.CreateExpectations() + .Url(CustomAppBaseUrl + $"/codename/{identifier.Codename}") + .HttpMethod(HttpMethod.Delete) + .Validate(); + } + + [Fact] + public async void DeleteCustomApp_IdentifierIsNull_Throws() + { + var client = _scenario.CreateManagementClient(); + + await client.Invoking(x => x.DeleteCustomAppAsync(null)).Should().ThrowAsync(); + } +} \ No newline at end of file diff --git a/Kontent.Ai.Management/IManagementClient.cs b/Kontent.Ai.Management/IManagementClient.cs index e45fe36a..d55ecbf0 100644 --- a/Kontent.Ai.Management/IManagementClient.cs +++ b/Kontent.Ai.Management/IManagementClient.cs @@ -4,6 +4,8 @@ using Kontent.Ai.Management.Models.Assets; using Kontent.Ai.Management.Models.Collections; using Kontent.Ai.Management.Models.Collections.Patch; +using Kontent.Ai.Management.Models.CustomApps; +using Kontent.Ai.Management.Models.CustomApps.Patch; using Kontent.Ai.Management.Models.Environments; using Kontent.Ai.Management.Models.Environments.Patch; using Kontent.Ai.Management.Models.Items; @@ -834,4 +836,34 @@ public interface IManagementClient /// /// A instance representing the web spotlight status. Task GetWebSpotlightStatusAsync(); + + /// + /// Returns list of custom apps. + /// + /// The instance that represents the custom app. + Task> ListCustomAppsAsync(); + + /// + /// Returns list of custom apps. + /// + /// The instance that represents the custom app. + Task GetCustomAppAsync(Reference identifier); + + /// + /// Returns list of custom apps. + /// + /// The instance that represents the custom app. + Task CreateCustomAppAsync(CustomAppCreateModel customApp); + + /// + /// Returns list of custom apps. + /// + /// The instance that represents the custom app. + Task DeleteCustomAppAsync(Reference identifier); + + /// + /// Returns list of custom apps. + /// + /// The instance that represents the custom app. + Task ModifyCustomAppAsync(Reference identifier, IEnumerable changes); } diff --git a/Kontent.Ai.Management/ManagementClient.CustomApp.cs b/Kontent.Ai.Management/ManagementClient.CustomApp.cs new file mode 100644 index 00000000..e3e9c637 --- /dev/null +++ b/Kontent.Ai.Management/ManagementClient.CustomApp.cs @@ -0,0 +1,62 @@ +using Kontent.Ai.Management.Models.CustomApps; +using Kontent.Ai.Management.Models.CustomApps.Patch; +using Kontent.Ai.Management.Models.Shared; +using System; +using System.Collections.Generic; +using System.Net.Http; +using System.Threading.Tasks; + +namespace Kontent.Ai.Management; + +public partial class ManagementClient +{ + /// + public async Task> ListCustomAppsAsync() + { + var endpointUrl = _urlBuilder.BuildCustomAppUrl(); + var response = await _actionInvoker.InvokeReadOnlyMethodAsync(endpointUrl, HttpMethod.Get); + + return new ListingResponseModel( + GetNextListingPageAsync, + response.Pagination?.Token, + endpointUrl, + response.CustomApps); + } + + /// + public async Task GetCustomAppAsync(Reference identifier) + { + ArgumentNullException.ThrowIfNull(identifier); + + var endpointUrl = _urlBuilder.BuildCustomAppUrl(identifier); + return await _actionInvoker.InvokeReadOnlyMethodAsync(endpointUrl, HttpMethod.Get); + } + + /// + public async Task CreateCustomAppAsync(CustomAppCreateModel customApp) + { + ArgumentNullException.ThrowIfNull(customApp); + + var endpointUrl = _urlBuilder.BuildCustomAppUrl(); + return await _actionInvoker.InvokeMethodAsync(endpointUrl, HttpMethod.Post, customApp); + } + + /// + public async Task DeleteCustomAppAsync(Reference identifier) + { + ArgumentNullException.ThrowIfNull(identifier); + + var endpointUrl = _urlBuilder.BuildCustomAppUrl(identifier); + await _actionInvoker.InvokeMethodAsync(endpointUrl, HttpMethod.Delete); + } + + /// + public async Task ModifyCustomAppAsync(Reference identifier, IEnumerable changes) + { + ArgumentNullException.ThrowIfNull(identifier); + ArgumentNullException.ThrowIfNull(changes); + + var endpointUrl = _urlBuilder.BuildCustomAppUrl(identifier); + return await _actionInvoker.InvokeMethodAsync, CustomAppModel>(endpointUrl, new HttpMethod("PATCH"), changes); + } +} \ No newline at end of file diff --git a/Kontent.Ai.Management/Models/CustomApps/CustomAppCreateModel.cs b/Kontent.Ai.Management/Models/CustomApps/CustomAppCreateModel.cs new file mode 100644 index 00000000..286bd54e --- /dev/null +++ b/Kontent.Ai.Management/Models/CustomApps/CustomAppCreateModel.cs @@ -0,0 +1,41 @@ +using Kontent.Ai.Management.Models.Shared; +using Newtonsoft.Json; +using System.Collections.Generic; + +namespace Kontent.Ai.Management.Models.CustomApps; + +/// +/// Represents the custom app create model. +/// +public class CustomAppCreateModel +{ + /// + /// Gets or sets the custom app's name. + /// + [JsonProperty("name")] + public string Name { get; init; } + + /// + /// Gets or sets the custom app's codename. + /// + [JsonProperty("codename")] + public string Codename { get; init; } + + /// + /// Gets or sets the custom app's source url. + /// + [JsonProperty("source_url")] + public string SourceUrl { get; init; } + + /// + /// Gets or sets the custom app's config. + /// + [JsonProperty("config")] + public string Config { get; init; } + + /// + /// Gets or sets the custom app's allowed roles. + /// + [JsonProperty("allowed_roles")] + public IReadOnlyCollection AllowedRoles { get; set; } +} \ No newline at end of file diff --git a/Kontent.Ai.Management/Models/CustomApps/CustomAppListingResponseServerModel.cs b/Kontent.Ai.Management/Models/CustomApps/CustomAppListingResponseServerModel.cs new file mode 100644 index 00000000..5aad0866 --- /dev/null +++ b/Kontent.Ai.Management/Models/CustomApps/CustomAppListingResponseServerModel.cs @@ -0,0 +1,20 @@ +using Kontent.Ai.Management.Models.Shared; +using Newtonsoft.Json; +using System.Collections; +using System.Collections.Generic; + +namespace Kontent.Ai.Management.Models.CustomApps; + +[JsonObject] +internal class CustomAppListingResponseServerModel : IListingResponse +{ + [JsonProperty("custom_apps")] + public IEnumerable CustomApps { get; set; } + + [JsonProperty("pagination")] + public PaginationResponseModel Pagination { get; set; } + + IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); + + public IEnumerator GetEnumerator() => CustomApps.GetEnumerator(); +} \ No newline at end of file diff --git a/Kontent.Ai.Management/Models/CustomApps/CustomAppModel.cs b/Kontent.Ai.Management/Models/CustomApps/CustomAppModel.cs new file mode 100644 index 00000000..d15f7565 --- /dev/null +++ b/Kontent.Ai.Management/Models/CustomApps/CustomAppModel.cs @@ -0,0 +1,48 @@ +using Kontent.Ai.Management.Models.Shared; +using Newtonsoft.Json; +using System; +using System.Collections.Generic; + +namespace Kontent.Ai.Management.Models.CustomApps; + +/// +/// Represents the custom app model. +/// +public class CustomAppModel +{ + /// + /// Gets or sets the custom app's internal ID. + /// + [JsonProperty("id")] + public Guid Id { get; init; } + + /// + /// Gets or sets the custom app's name. + /// + [JsonProperty("name")] + public string Name { get; init; } + + /// + /// Gets or sets the custom app's codename. + /// + [JsonProperty("codename")] + public string Codename { get; init; } + + /// + /// Gets or sets the custom app's source url. + /// + [JsonProperty("source_url")] + public string SourceUrl { get; init; } + + /// + /// Gets or sets the custom app's config. + /// + [JsonProperty("config")] + public string Config { get; init; } + + /// + /// Gets or sets the custom app's allowed roles. + /// + [JsonProperty("allowed_roles")] + public IReadOnlyCollection AllowedRoles { get; set; } +} \ No newline at end of file diff --git a/Kontent.Ai.Management/Models/CustomApps/Patch/CustomAppAddIntoPatchModel.cs b/Kontent.Ai.Management/Models/CustomApps/Patch/CustomAppAddIntoPatchModel.cs new file mode 100644 index 00000000..542809af --- /dev/null +++ b/Kontent.Ai.Management/Models/CustomApps/Patch/CustomAppAddIntoPatchModel.cs @@ -0,0 +1,13 @@ +namespace Kontent.Ai.Management.Models.CustomApps.Patch; + +/// +/// Represents the addInto operation. +/// More info: https://kontent.ai/learn/reference/management-api-v2#operation/modify-a-custom-app +/// +public class CustomAppAddIntoPatchModel : CustomAppOperationBaseModel +{ + /// + /// Represents the addInto operation. + /// + public override string Op => "addInto"; +} \ No newline at end of file diff --git a/Kontent.Ai.Management/Models/CustomApps/Patch/CustomAppOperationBaseModel.cs b/Kontent.Ai.Management/Models/CustomApps/Patch/CustomAppOperationBaseModel.cs new file mode 100644 index 00000000..db6f9339 --- /dev/null +++ b/Kontent.Ai.Management/Models/CustomApps/Patch/CustomAppOperationBaseModel.cs @@ -0,0 +1,30 @@ +using Newtonsoft.Json; + +namespace Kontent.Ai.Management.Models.CustomApps.Patch; + +/// +/// Represents the operation on the custom app. +/// More info: https://kontent.ai/learn/reference/management-api-v2#operation/modify-a-custom-app +/// +public abstract class CustomAppOperationBaseModel +{ + /// + /// Gets specification of the operation to perform. + /// More info: https://kontent.ai/learn/reference/management-api-v2#operation/modify-a-custom-app + /// + [JsonProperty("op")] + public abstract string Op { get; } + + /// + /// Gets or sets the name of the property to modify. + /// + [JsonProperty("property_name", Required = Required.Always)] + public PropertyName PropertyName { get; set; } + + /// + /// Gets or sets the value to replace into the property specified in the path where the format depends on the specific property. + /// More info: https://kontent.ai/learn/reference/management-api-v2#operation/modify-a-custom-app + /// + [JsonProperty("value")] + public dynamic Value { get; set; } +} \ No newline at end of file diff --git a/Kontent.Ai.Management/Models/CustomApps/Patch/CustomAppRemovePatchModel.cs b/Kontent.Ai.Management/Models/CustomApps/Patch/CustomAppRemovePatchModel.cs new file mode 100644 index 00000000..22e489a2 --- /dev/null +++ b/Kontent.Ai.Management/Models/CustomApps/Patch/CustomAppRemovePatchModel.cs @@ -0,0 +1,13 @@ +namespace Kontent.Ai.Management.Models.CustomApps.Patch; + +/// +/// Represents the remove operation. +/// More info: https://kontent.ai/learn/reference/management-api-v2#operation/modify-a-custom-app +/// +public class CustomAppRemovePatchModel : CustomAppOperationBaseModel +{ + /// + /// Represents the remove operation. + /// + public override string Op => "remove"; +} \ No newline at end of file diff --git a/Kontent.Ai.Management/Models/CustomApps/Patch/CustomAppReplacePatchModel.cs b/Kontent.Ai.Management/Models/CustomApps/Patch/CustomAppReplacePatchModel.cs new file mode 100644 index 00000000..ef1a9ebd --- /dev/null +++ b/Kontent.Ai.Management/Models/CustomApps/Patch/CustomAppReplacePatchModel.cs @@ -0,0 +1,13 @@ +namespace Kontent.Ai.Management.Models.CustomApps.Patch; + +/// +/// Represents the replace operation. +/// More info: https://kontent.ai/learn/reference/management-api-v2#operation/modify-a-custom-app +/// +public class CustomAppReplacePatchModel : CustomAppOperationBaseModel +{ + /// + /// Represents the replace operation. + /// + public override string Op => "replace"; +} \ No newline at end of file diff --git a/Kontent.Ai.Management/Models/CustomApps/Patch/PropertyName.cs b/Kontent.Ai.Management/Models/CustomApps/Patch/PropertyName.cs new file mode 100644 index 00000000..be79711e --- /dev/null +++ b/Kontent.Ai.Management/Models/CustomApps/Patch/PropertyName.cs @@ -0,0 +1,39 @@ +using System.Runtime.Serialization; + +namespace Kontent.Ai.Management.Models.CustomApps.Patch; + +/// +/// Represents enum of properties that can be replaced in the custom app. +/// +public enum PropertyName +{ + /// + /// The custom app's name. + /// + [EnumMember(Value = "name")] + Name, + + /// + /// The custom app's codename. + /// + [EnumMember(Value = "codename")] + Codename, + + /// + /// The custom app's source url. + /// + [EnumMember(Value = "source_url")] + SourceUrl, + + /// + /// The custom app's config. + /// + [EnumMember(Value = "Config")] + Config, + + /// + /// The custom app's allowed_roles. + /// + [EnumMember(Value = "allowed_roles")] + AllowedRoles +} \ No newline at end of file diff --git a/Kontent.Ai.Management/Modules/UrlBuilder/EndpointUrlBuilder.cs b/Kontent.Ai.Management/Modules/UrlBuilder/EndpointUrlBuilder.cs index 063ea0fc..d0f3aaaf 100644 --- a/Kontent.Ai.Management/Modules/UrlBuilder/EndpointUrlBuilder.cs +++ b/Kontent.Ai.Management/Modules/UrlBuilder/EndpointUrlBuilder.cs @@ -32,6 +32,7 @@ internal sealed class EndpointUrlBuilder private readonly EnvironmentRolesTemplate _environmentRolesTemplate; private readonly UserTemplate _userTemplate; private readonly WebSpotlightTemplate _webSpotlightTemplate; + private readonly CustomAppTemplate _customAppTemplate; private readonly ManagementOptions _options; @@ -54,6 +55,7 @@ public EndpointUrlBuilder(ManagementOptions options) _environmentRolesTemplate = new EnvironmentRolesTemplate(); _userTemplate = new UserTemplate(); _webSpotlightTemplate = new WebSpotlightTemplate(); + _customAppTemplate = new CustomAppTemplate(); _options = options; } @@ -226,6 +228,10 @@ public string BuildSubscriptionUserDeactivateDisableUrl(UserIdentifier identifie public string BuildMarkEnvironmentAsProductionUrl() => GetEnvironmentUrl("/mark-environment-as-production"); public string BuildWebSpotlightUrl() => GetEnvironmentUrl(_webSpotlightTemplate.Url); + + public string BuildCustomAppUrl() => GetEnvironmentUrl(_customAppTemplate.Url); + + public string BuildCustomAppUrl(Reference identifier) => GetEnvironmentUrl(string.Concat(_customAppTemplate.GetIdentifierUrlSegment(identifier))); private string GetEnvironmentUrl(string path, params string[] parameters) => GetUrl(BuildEnvironmentUrl(), path, parameters); diff --git a/Kontent.Ai.Management/Modules/UrlBuilder/Templates/CustomAppTemplate.cs b/Kontent.Ai.Management/Modules/UrlBuilder/Templates/CustomAppTemplate.cs new file mode 100644 index 00000000..b2a1b686 --- /dev/null +++ b/Kontent.Ai.Management/Modules/UrlBuilder/Templates/CustomAppTemplate.cs @@ -0,0 +1,11 @@ +using System; + +namespace Kontent.Ai.Management.Modules.UrlBuilder.Templates; + +internal class CustomAppTemplate : UrlTemplate +{ + public override string Url => "/custom-apps"; + public override string UrlId => "/custom-apps/{0}"; + public override string UrlCodename => "/custom-apps/codename/{0}"; + public override string UrlExternalId => throw new InvalidOperationException("Custom apps do not have external id url"); +} \ No newline at end of file