diff --git a/README.md b/README.md index cdcf1b7..b4db27b 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,6 @@ -# PushCrew.Net -.Net implementation of [PushCrew API](https://api.pushcrew.com) -[![codecov](https://codecov.io/gh/GregsStack/PushCrew.Net/branch/master/graph/badge.svg)](https://codecov.io/gh/GregsStack/PushCrew.Net) +# VwoEngage.Net +.Net implementation of [VWO Engage](https://vwo.com/engage/) (former [PushCrew API](https://api.pushcrew.com)) + +[![Nuget (with prereleases)](https://img.shields.io/nuget/vpre/GregsStack.VwoEngage.Net.Api)](https://www.nuget.org/packages/GregsStack.VwoEngage.Net.Api) +[![Build Status](https://dev.azure.com/GregsStack/GitHub/_apis/build/status/GregsStack.VwoEngage.Net?branchName=master)](https://dev.azure.com/GregsStack/GitHub/_build/latest?definitionId=4&branchName=master) +[![codecov](https://codecov.io/gh/GregsStack/VwoEngage.Net/branch/master/graph/badge.svg)](https://codecov.io/gh/GregsStack/VwoEngage.Net) diff --git a/PushCrew.Net.sln b/VwoEngage.Net.sln similarity index 91% rename from PushCrew.Net.sln rename to VwoEngage.Net.sln index dbc9c1f..3e203c6 100644 --- a/PushCrew.Net.sln +++ b/VwoEngage.Net.sln @@ -5,22 +5,25 @@ VisualStudioVersion = 16.0.29123.89 MinimumVisualStudioVersion = 15.0.26124.0 Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{2384891B-7AE9-408A-9DB0-AB41ED0F41C2}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "GregsStack.PushCrew.Net.Api", "src\GregsStack.PushCrew.Net.Api\GregsStack.PushCrew.Net.Api.csproj", "{CABE94FF-0D04-4C74-B62B-02785FBADEFD}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "GregsStack.VwoEngage.Net.Api", "src\GregsStack.VwoEngage.Net.Api\GregsStack.VwoEngage.Net.Api.csproj", "{CABE94FF-0D04-4C74-B62B-02785FBADEFD}" EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tests", "tests", "{BB3C3681-2D2A-4F08-B7CE-68461C0643B6}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "GregsStack.PushCrew.Net.Api.Tests", "tests\GregsStack.PushCrew.Net.Api.Tests\GregsStack.PushCrew.Net.Api.Tests.csproj", "{8EDEE3E3-7899-49AC-9271-F685890D83F0}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "GregsStack.VwoEngage.Net.Api.Tests", "tests\GregsStack.VwoEngage.Net.Api.Tests\GregsStack.VwoEngage.Net.Api.Tests.csproj", "{8EDEE3E3-7899-49AC-9271-F685890D83F0}" EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{45050F49-6359-48E6-B1D1-CBF29026D8D8}" ProjectSection(SolutionItems) = preProject + .editorconfig = .editorconfig .gitignore = .gitignore + azure-pipelines.yml = azure-pipelines.yml + GitVersion.yml = GitVersion.yml LICENSE = LICENSE README.md = README.md EndProjectSection EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tools", "tools", "{130AB2DF-DA26-436C-AEE2-B644473D3AD4}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GregsStack.NuGet.Tools", "tools\GregsStack.NuGet.Tools\GregsStack.NuGet.Tools.csproj", "{1597AD92-5743-46A3-9E09-7F91BA6FDC89}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "GregsStack.NuGet.Tools", "tools\GregsStack.NuGet.Tools\GregsStack.NuGet.Tools.csproj", "{1597AD92-5743-46A3-9E09-7F91BA6FDC89}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution diff --git a/azure-pipelines.yml b/azure-pipelines.yml index 4f80d49..0045aa7 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -65,12 +65,13 @@ steps: - powershell: | $testsDirectory = Join-Path -Path '$(Build.SourcesDirectory)' -ChildPath 'tests' $coverageFile = Get-ChildItem -Path "$testsDirectory" -Recurse -File -Filter 'coverage.xml' | Select-Object -First 1 - & "$ENV:USERPROFILE\.nuget\packages\codecov\1.7.1\tools\codecov.exe" -f "$($coverageFile.FullName)" -t '$(CODECOV_TOKEN)' + $codecovExecutable = Get-ChildItem -Path "$ENV:USERPROFILE\.nuget\packages\codecov" -Recurse -File -Filter "codecov.exe" | Sort-Object -Descending -Property FullName | Select-Object -First 1 + & "$($codecovExecutable.FullName)" -f "$($coverageFile.FullName)" -t '$(CODECOV_TOKEN)' displayName: 'Upload coverage to codecov.io' - task: DotNetCoreCLI@2 displayName: 'Pack projects' - condition: and(succeeded(), or(eq(variables['Build.SourceBranch'], 'refs/heads/master'), startsWith(variables['Build.SourceBranch'], 'refs/heads/release/'))) + condition: and(succeeded(), or(eq(variables['Build.SourceBranch'], 'refs/heads/master'), eq(variables['Build.SourceBranch'], 'refs/heads/develop'), startsWith(variables['Build.SourceBranch'], 'refs/heads/release/'))) inputs: command: 'pack' packagesToPack: '$(Solution)' @@ -80,14 +81,14 @@ steps: versionEnvVar: 'GITVERSION_NUGETVERSIONV2' - task: NuGetToolInstaller@1 - condition: and(succeeded(), or(eq(variables['Build.SourceBranch'], 'refs/heads/master'), startsWith(variables['Build.SourceBranch'], 'refs/heads/release/'))) + condition: and(succeeded(), or(eq(variables['Build.SourceBranch'], 'refs/heads/master'), eq(variables['Build.SourceBranch'], 'refs/heads/develop'), startsWith(variables['Build.SourceBranch'], 'refs/heads/release/'))) inputs: versionSpec: '5.1.0' checkLatest: true - task: NuGetCommand@2 displayName: 'Push *.nupkg and *.snupkg to nuget.org' - condition: and(succeeded(), or(eq(variables['Build.SourceBranch'], 'refs/heads/master'), startsWith(variables['Build.SourceBranch'], 'refs/heads/release/'))) + condition: and(succeeded(), or(eq(variables['Build.SourceBranch'], 'refs/heads/master'), eq(variables['Build.SourceBranch'], 'refs/heads/develop'), startsWith(variables['Build.SourceBranch'], 'refs/heads/release/'))) inputs: command: 'push' packagesToPush: '$(Build.ArtifactStagingDirectory)/**/*.nupkg;!$(Build.ArtifactStagingDirectory)/**/*.symbols.nupkg' @@ -103,8 +104,8 @@ steps: action: 'create' target: '$(Build.SourceVersion)' tagSource: 'manual' - tag: '$(Build.BuildNumber)' - title: '$(Build.BuildNumber)' + tag: 'v$(Build.BuildNumber)' + title: 'v$(Build.BuildNumber)' - task: GitHubRelease@0 condition: and(succeeded(), or(eq(variables['Build.SourceBranch'], 'refs/heads/develop'), startsWith(variables['Build.SourceBranch'], 'refs/heads/release/'))) @@ -114,6 +115,6 @@ steps: action: 'create' target: '$(Build.SourceVersion)' tagSource: 'manual' - tag: '$(Build.BuildNumber)' - title: '$(Build.BuildNumber)' + tag: 'v$(Build.BuildNumber)' + title: 'v$(Build.BuildNumber)' isPreRelease: true \ No newline at end of file diff --git a/src/GregsStack.PushCrew.Net.Api/Client.cs b/src/GregsStack.PushCrew.Net.Api/Client.cs deleted file mode 100644 index 2956a44..0000000 --- a/src/GregsStack.PushCrew.Net.Api/Client.cs +++ /dev/null @@ -1,217 +0,0 @@ -namespace GregsStack.PushCrew.Net.Api -{ - using System; - using System.Collections.Generic; - using System.Net; - using System.Net.Http; - using System.Net.Http.Formatting; - using System.Net.Http.Headers; - using System.Threading.Tasks; - - using Converters; - - using Exceptions; - - using Newtonsoft.Json; - using Newtonsoft.Json.Converters; - using Newtonsoft.Json.Serialization; - - using Request; - - using Response; - - public class Client : IDisposable - { - private readonly HttpClient client; - private readonly JsonMediaTypeFormatter jsonFormatter; - private readonly Uri baseUri; - - public Client(string token) - : this(token, new Uri("https://pushcrew.com/api/v1/")) - { - } - - public Client(string token, Uri baseUri) - { - var apiToken = token ?? throw new ArgumentNullException(nameof(token)); - this.client = new HttpClient(); - this.client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("key", apiToken); - - this.baseUri = baseUri ?? throw new ArgumentNullException(nameof(baseUri)); - - var contractResolver = new DefaultContractResolver { NamingStrategy = new SnakeCaseNamingStrategy() }; - this.jsonFormatter = new JsonMediaTypeFormatter - { - SerializerSettings = - { - ContractResolver = contractResolver - } - }; - - this.jsonFormatter.SerializerSettings.Converters.Add(new StringEnumConverter()); - this.jsonFormatter.SerializerSettings.Converters.Add(new BooleanConverter()); - } - - public async Task SendAllSubscribers(SendMessageRequest request) - { - var uri = new Uri(this.baseUri, "send/all"); - return await this.PostAsync(request, uri); - } - - public async Task SendSubscribersInSegment(SendMessageRequest request, long segmentId) - { - var uri = new Uri(this.baseUri, $"send/segment/{segmentId}"); - return await this.PostAsync(request, uri); - } - - public async Task SendSubscribers(SendMessageRequest request, ICollection subscriberList) - { - var uri = new Uri(this.baseUri, "send/list"); - - var validRequest = request ?? throw new ArgumentNullException(nameof(request)); - var validSubscriberList = subscriberList ?? throw new ArgumentNullException(nameof(subscriberList)); - var subscribers = new Dictionary> { { "subscriber_list", validSubscriberList } }; - var subscriberRequest = (SendMessageSubscribersRequest)validRequest; - subscriberRequest.SubscriberList = JsonConvert.SerializeObject(subscribers); - - return await this.PostAsync(subscriberRequest, uri); - } - - public async Task SendSubscriber(SendMessageRequest request, string subscriberId) - { - var uri = new Uri(this.baseUri, "send/individual"); - - var validRequest = request ?? throw new ArgumentNullException(nameof(request)); - var validSubscriberId = subscriberId ?? throw new ArgumentNullException(nameof(subscriberId)); - var subscriberRequest = (SendMessageSubscriberRequest)validRequest; - subscriberRequest.SubscriberId = validSubscriberId; - - return await this.PostAsync(subscriberRequest, uri); - } - - public async Task CheckNotificationRequestStatus(string id) - { - var uri = new Uri(this.baseUri, $"checkstatus/{id}"); - return await this.GetAsync(uri); - } - - public async Task ScheduleAllSubscribers(ScheduleMessageRequest request) - { - var uri = new Uri(this.baseUri, "send/all"); - return await this.PostAsync(request, uri); - } - - public async Task ScheduleSegment(ScheduleMessageRequest request, string segmentId) - { - var uri = new Uri(this.baseUri, $"send/segment/{segmentId}"); - return await this.PostAsync(request, uri); - } - - public async Task AddSegment(string name) - { - var uri = new Uri(this.baseUri, "segments"); - var dict = new Dictionary { { "name", name } }; - return await this.PostAsync, SegmentResponse>(dict, uri); - } - - public async Task ListSegments() - { - var uri = new Uri(this.baseUri, "segments"); - return await this.GetAsync(uri); - } - - public async Task AddSubscribersToSegment(string segmentId, ICollection subscriberList) - { - var uri = new Uri(this.baseUri, $"segments/{segmentId}/subscribers"); - - var validSubscriberList = subscriberList ?? throw new ArgumentNullException(nameof(subscriberList)); - var subscribers = new Dictionary> { { "subscriber_list", validSubscriberList } }; - var subscriberRequest = new SendMessageSubscribersRequest - { - SubscriberList = JsonConvert.SerializeObject(subscribers) - }; - - return await this.PostAsync(subscriberRequest, uri); - } - - public async Task ListSubscribersInSegment(string segmentId, int page = 1, int perPage = 1024) - { - var uri = new Uri(this.baseUri, $"segments/{segmentId}/subscribers?page={page}&per_page={perPage}"); - return await this.GetAsync(uri); - } - - public async Task ListSegmentsOfSubscriber(string subscriberId) - { - var uri = new Uri(this.baseUri, $"subscribers/{subscriberId}/segments"); - return await this.GetAsync(uri); - } - - public async Task RemoveSubscribers(long segmentId, RemoveSubscriberRequest removeSubscriberRequest) - { - var uri = new Uri(this.baseUri, $"segments/{segmentId}/subscribers"); - var response = await this.client.PutAsync(uri, removeSubscriberRequest, this.jsonFormatter); - await this.VerifyResponse(response); - return await this.ReadAsAsync(response); - } - - public async Task DeleteSegment(long segmentId) - { - var uri = new Uri(this.baseUri, $"segments/{segmentId}"); - var response = await this.client.DeleteAsync(uri); - await this.VerifyResponse(response); - return await this.ReadAsAsync(response); - } - - public void Dispose() - { - this.client?.Dispose(); - } - - private async Task PostAsync(TRequest request, Uri uri) - where TRequest : class - where TResponse : class - { - var response = await this.client.PostAsync(uri, request.ToFormUrlEncodedContent()); - await this.VerifyResponse(response); - return await this.ReadAsAsync(response); - } - - private async Task GetAsync(Uri uri) - where TResponse : class - { - var response = await this.client.GetAsync(uri); - await this.VerifyResponse(response); - return await this.ReadAsAsync(response); - } - - private async Task VerifyResponse(HttpResponseMessage response) - { - switch (response.StatusCode) - { - case HttpStatusCode.Unauthorized: - { - var exception = await this.ReadAsAsync(response); - throw exception; - } - - case HttpStatusCode.BadRequest: - { - var exception = await this.ReadAsAsync(response); - throw exception; - } - - case HttpStatusCode.InternalServerError: - { - var exception = await this.ReadAsAsync(response); - throw exception; - } - } - } - - private async Task ReadAsAsync(HttpResponseMessage response) - where TResponse : class - { - return await response.Content.ReadAsAsync(new[] { this.jsonFormatter }); - } - } -} diff --git a/src/GregsStack.PushCrew.Net.Api/Exceptions/BadRequestException.cs b/src/GregsStack.PushCrew.Net.Api/Exceptions/BadRequestException.cs deleted file mode 100644 index f025e6a..0000000 --- a/src/GregsStack.PushCrew.Net.Api/Exceptions/BadRequestException.cs +++ /dev/null @@ -1,17 +0,0 @@ -namespace GregsStack.PushCrew.Net.Api.Exceptions -{ - using System.Collections.Generic; - - public class BadRequestException : PushCrewException - { - /// - /// To denote whether push request succeeded or not. Values can be or . - /// - public Status Status { get; set; } - - /// - /// Invalid subscriber IDs. - /// - public ICollection InvalidList { get; set; } - } -} diff --git a/src/GregsStack.PushCrew.Net.Api/Exceptions/InternalServerErrorException.cs b/src/GregsStack.PushCrew.Net.Api/Exceptions/InternalServerErrorException.cs deleted file mode 100644 index 9a8f712..0000000 --- a/src/GregsStack.PushCrew.Net.Api/Exceptions/InternalServerErrorException.cs +++ /dev/null @@ -1,10 +0,0 @@ -namespace GregsStack.PushCrew.Net.Api.Exceptions -{ - public class InternalServerErrorException : PushCrewException - { - /// - /// To denote whether push request succeeded or not. Values can be or . - /// - public Status Status { get; set; } - } -} diff --git a/src/GregsStack.PushCrew.Net.Api/Exceptions/PushCrewException.cs b/src/GregsStack.PushCrew.Net.Api/Exceptions/PushCrewException.cs deleted file mode 100644 index 6587cfd..0000000 --- a/src/GregsStack.PushCrew.Net.Api/Exceptions/PushCrewException.cs +++ /dev/null @@ -1,8 +0,0 @@ -namespace GregsStack.PushCrew.Net.Api.Exceptions -{ - using System; - - public class PushCrewException : Exception - { - } -} diff --git a/src/GregsStack.PushCrew.Net.Api/Exceptions/UnauthorizedException.cs b/src/GregsStack.PushCrew.Net.Api/Exceptions/UnauthorizedException.cs deleted file mode 100644 index b3ee8d3..0000000 --- a/src/GregsStack.PushCrew.Net.Api/Exceptions/UnauthorizedException.cs +++ /dev/null @@ -1,6 +0,0 @@ -namespace GregsStack.PushCrew.Net.Api.Exceptions -{ - public class UnauthorizedException : PushCrewException - { - } -} diff --git a/src/GregsStack.PushCrew.Net.Api/GregsStack.PushCrew.Net.Api.csproj b/src/GregsStack.PushCrew.Net.Api/GregsStack.PushCrew.Net.Api.csproj deleted file mode 100644 index 4fc6e7c..0000000 --- a/src/GregsStack.PushCrew.Net.Api/GregsStack.PushCrew.Net.Api.csproj +++ /dev/null @@ -1,11 +0,0 @@ - - - - netstandard2.0 - - - - - - - diff --git a/src/GregsStack.PushCrew.Net.Api/Attributes/ValidatedNotNullAttribute.cs b/src/GregsStack.VwoEngage.Net.Api/Attributes/ValidatedNotNullAttribute.cs similarity index 64% rename from src/GregsStack.PushCrew.Net.Api/Attributes/ValidatedNotNullAttribute.cs rename to src/GregsStack.VwoEngage.Net.Api/Attributes/ValidatedNotNullAttribute.cs index d26c410..eb2a6e4 100644 --- a/src/GregsStack.PushCrew.Net.Api/Attributes/ValidatedNotNullAttribute.cs +++ b/src/GregsStack.VwoEngage.Net.Api/Attributes/ValidatedNotNullAttribute.cs @@ -1,4 +1,4 @@ -namespace GregsStack.PushCrew.Net.Api.Attributes +namespace GregsStack.VwoEngage.Net.Api.Attributes { using System; diff --git a/src/GregsStack.PushCrew.Net.Api/Converters/BooleanConverter.cs b/src/GregsStack.VwoEngage.Net.Api/Converters/BooleanConverter.cs similarity index 93% rename from src/GregsStack.PushCrew.Net.Api/Converters/BooleanConverter.cs rename to src/GregsStack.VwoEngage.Net.Api/Converters/BooleanConverter.cs index 4c14f86..9bebbed 100644 --- a/src/GregsStack.PushCrew.Net.Api/Converters/BooleanConverter.cs +++ b/src/GregsStack.VwoEngage.Net.Api/Converters/BooleanConverter.cs @@ -1,4 +1,4 @@ -namespace GregsStack.PushCrew.Net.Api.Converters +namespace GregsStack.VwoEngage.Net.Api.Converters { using System; diff --git a/src/GregsStack.PushCrew.Net.Api/Converters/FormUrlEncodedContentConverter.cs b/src/GregsStack.VwoEngage.Net.Api/Converters/FormUrlEncodedContentConverter.cs similarity index 96% rename from src/GregsStack.PushCrew.Net.Api/Converters/FormUrlEncodedContentConverter.cs rename to src/GregsStack.VwoEngage.Net.Api/Converters/FormUrlEncodedContentConverter.cs index 1247c61..df80d31 100644 --- a/src/GregsStack.PushCrew.Net.Api/Converters/FormUrlEncodedContentConverter.cs +++ b/src/GregsStack.VwoEngage.Net.Api/Converters/FormUrlEncodedContentConverter.cs @@ -1,4 +1,4 @@ -namespace GregsStack.PushCrew.Net.Api.Converters +namespace GregsStack.VwoEngage.Net.Api.Converters { using System; using System.Collections.Generic; diff --git a/src/GregsStack.PushCrew.Net.Api/Converters/TimeSpanConverter.cs b/src/GregsStack.VwoEngage.Net.Api/Converters/TimeSpanConverter.cs similarity index 95% rename from src/GregsStack.PushCrew.Net.Api/Converters/TimeSpanConverter.cs rename to src/GregsStack.VwoEngage.Net.Api/Converters/TimeSpanConverter.cs index 1b0ac35..36d787e 100644 --- a/src/GregsStack.PushCrew.Net.Api/Converters/TimeSpanConverter.cs +++ b/src/GregsStack.VwoEngage.Net.Api/Converters/TimeSpanConverter.cs @@ -1,4 +1,4 @@ -namespace GregsStack.PushCrew.Net.Api.Converters +namespace GregsStack.VwoEngage.Net.Api.Converters { using System; diff --git a/src/GregsStack.VwoEngage.Net.Api/Exceptions/BadRequestException.cs b/src/GregsStack.VwoEngage.Net.Api/Exceptions/BadRequestException.cs new file mode 100644 index 0000000..1996b74 --- /dev/null +++ b/src/GregsStack.VwoEngage.Net.Api/Exceptions/BadRequestException.cs @@ -0,0 +1,36 @@ +namespace GregsStack.VwoEngage.Net.Api.Exceptions +{ + using System; + using System.Collections.Generic; + using System.Runtime.Serialization; + + using Extensions; + + using Response.Models; + + public class BadRequestException : VwoEngageException + { + /// + public BadRequestException(SerializationInfo info, StreamingContext context) + : base(info, context) + { + var statusString = info.GetSafeString("status"); + if (Enum.TryParse(statusString, true, out Status status)) + { + this.Status = status; + } + + this.InvalidList = info.GetSafeValue("invalid_list", new List()); + } + + /// + /// To denote whether push request succeeded or not. Values can be or . + /// + public Status Status { get; set; } + + /// + /// Invalid subscriber IDs. + /// + public ICollection InvalidList { get; set; } + } +} diff --git a/src/GregsStack.VwoEngage.Net.Api/Exceptions/InternalServerErrorException.cs b/src/GregsStack.VwoEngage.Net.Api/Exceptions/InternalServerErrorException.cs new file mode 100644 index 0000000..9191464 --- /dev/null +++ b/src/GregsStack.VwoEngage.Net.Api/Exceptions/InternalServerErrorException.cs @@ -0,0 +1,28 @@ +namespace GregsStack.VwoEngage.Net.Api.Exceptions +{ + using System; + using System.Runtime.Serialization; + + using Extensions; + + using Response.Models; + + public class InternalServerErrorException : VwoEngageException + { + /// + public InternalServerErrorException(SerializationInfo info, StreamingContext context) + : base(info, context) + { + var statusString = info.GetSafeString("status"); + if (Enum.TryParse(statusString, true, out Status status)) + { + this.Status = status; + } + } + + /// + /// To denote whether push request succeeded or not. Values can be or . + /// + public Status Status { get; set; } + } +} diff --git a/src/GregsStack.VwoEngage.Net.Api/Exceptions/UnauthorizedException.cs b/src/GregsStack.VwoEngage.Net.Api/Exceptions/UnauthorizedException.cs new file mode 100644 index 0000000..3000349 --- /dev/null +++ b/src/GregsStack.VwoEngage.Net.Api/Exceptions/UnauthorizedException.cs @@ -0,0 +1,13 @@ +namespace GregsStack.VwoEngage.Net.Api.Exceptions +{ + using System.Runtime.Serialization; + + public class UnauthorizedException : VwoEngageException + { + /// + public UnauthorizedException(SerializationInfo info, StreamingContext context) + : base(info, context) + { + } + } +} diff --git a/src/GregsStack.VwoEngage.Net.Api/Exceptions/VwoEngageException.cs b/src/GregsStack.VwoEngage.Net.Api/Exceptions/VwoEngageException.cs new file mode 100644 index 0000000..cd76490 --- /dev/null +++ b/src/GregsStack.VwoEngage.Net.Api/Exceptions/VwoEngageException.cs @@ -0,0 +1,20 @@ +namespace GregsStack.VwoEngage.Net.Api.Exceptions +{ + using System; + using System.Runtime.Serialization; + + using Extensions; + + public class VwoEngageException : Exception + { + /// Initializes a new instance of the class with serialized data. + /// The that holds the serialized object data about the exception being thrown. + /// The that contains contextual information about the source or destination. + /// The parameter is null. + /// The class name is null or is zero (0). + public VwoEngageException(SerializationInfo info, StreamingContext context) + : base(info.GetSafeString("message") ?? info.GetSafeString("error")) + { + } + } +} diff --git a/src/GregsStack.VwoEngage.Net.Api/Extensions/SerializationInfoExtension.cs b/src/GregsStack.VwoEngage.Net.Api/Extensions/SerializationInfoExtension.cs new file mode 100644 index 0000000..cf0b5e0 --- /dev/null +++ b/src/GregsStack.VwoEngage.Net.Api/Extensions/SerializationInfoExtension.cs @@ -0,0 +1,27 @@ +namespace GregsStack.VwoEngage.Net.Api.Extensions +{ + using System; + using System.Runtime.Serialization; + + using Attributes; + + public static class SerializationInfoExtension + { + public static T GetSafeValue([ValidatedNotNull] this SerializationInfo info, [ValidatedNotNull] string name, T defaultValue = default) + { + var serializationInfo = info ?? throw new ArgumentNullException(nameof(info)); + var elementName = name ?? throw new ArgumentNullException(nameof(name)); + + try + { + return (T)serializationInfo.GetValue(elementName, typeof(T)); + } + catch (SerializationException) + { + return defaultValue; + } + } + + public static string GetSafeString([ValidatedNotNull] this SerializationInfo info, [ValidatedNotNull] string name, string defaultValue = default) => info.GetSafeValue(name, defaultValue); + } +} diff --git a/src/GregsStack.VwoEngage.Net.Api/Extensions/ServiceCollectionExtension.cs b/src/GregsStack.VwoEngage.Net.Api/Extensions/ServiceCollectionExtension.cs new file mode 100644 index 0000000..a15f90f --- /dev/null +++ b/src/GregsStack.VwoEngage.Net.Api/Extensions/ServiceCollectionExtension.cs @@ -0,0 +1,23 @@ +namespace GregsStack.VwoEngage.Net.Api.Extensions +{ + using System; + using System.Net.Http.Headers; + + using Attributes; + + using Microsoft.Extensions.DependencyInjection; + + public static class ServiceCollectionExtension + { + public static IHttpClientBuilder AddPushCrewHttpClient([ValidatedNotNull] this IServiceCollection serviceCollection, [ValidatedNotNull] string apiToken) + { + var validatedServiceCollection = serviceCollection ?? throw new ArgumentNullException(nameof(serviceCollection)); + var validatedApiToken = apiToken ?? throw new ArgumentNullException(nameof(apiToken)); + return validatedServiceCollection.AddHttpClient(VwoEngageConfiguration.ClientName, client => + { + client.BaseAddress = VwoEngageConfiguration.BaseUri; + client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue(validatedApiToken); + }); + } + } +} diff --git a/src/GregsStack.VwoEngage.Net.Api/GregsStack.PushCrew.Net.Api.csproj b/src/GregsStack.VwoEngage.Net.Api/GregsStack.PushCrew.Net.Api.csproj new file mode 100644 index 0000000..88a5509 --- /dev/null +++ b/src/GregsStack.VwoEngage.Net.Api/GregsStack.PushCrew.Net.Api.csproj @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/src/GregsStack.VwoEngage.Net.Api/GregsStack.VwoEngage.Net.Api.csproj b/src/GregsStack.VwoEngage.Net.Api/GregsStack.VwoEngage.Net.Api.csproj new file mode 100644 index 0000000..adaae44 --- /dev/null +++ b/src/GregsStack.VwoEngage.Net.Api/GregsStack.VwoEngage.Net.Api.csproj @@ -0,0 +1,36 @@ + + + + netstandard2.0 + LICENSE + + https://github.com/GregsStack/VwoEngage.Net + https://github.com/GregsStack/VwoEngage.Net + pushcrew;api-client;api;pushcrew-api;vwo;engage;vwo-engage; + Gregor Sindl + Copyright © 2019 Gregor Sindl + .Net implementation of VWO Engage (former PushCrew) API (https://api.pushcrew.com) + true + true + true + true + snupkg + + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + True + + + + + diff --git a/src/GregsStack.VwoEngage.Net.Api/IVwoEngageClient.cs b/src/GregsStack.VwoEngage.Net.Api/IVwoEngageClient.cs new file mode 100644 index 0000000..0e12e5a --- /dev/null +++ b/src/GregsStack.VwoEngage.Net.Api/IVwoEngageClient.cs @@ -0,0 +1,27 @@ +namespace GregsStack.VwoEngage.Net.Api +{ + using System.Collections.Generic; + using System.Threading.Tasks; + + using Request; + + using Response; + + public interface IVwoEngageClient + { + Task SendAllSubscribersAsync(SendMessageRequest request); + Task SendSubscribersInSegmentAsync(long segmentId, SendMessageRequest request); + Task SendSubscribersAsync(ICollection subscriberList, SendMessageRequest request); + Task SendSubscriberAsync(string subscriberId, SendMessageRequest request); + Task CheckNotificationRequestStatusAsync(string id); + Task ScheduleAllSubscribersAsync(ScheduleMessageRequest request); + Task ScheduleSegmentAsync(long segmentId, ScheduleMessageRequest request); + Task AddSegmentAsync(string name); + Task ListSegmentsAsync(); + Task AddSubscribersToSegmentAsync(long segmentId, ICollection subscriberList); + Task ListSubscribersInSegmentAsync(long segmentId, int page = 1, int perPage = 1024); + Task ListSegmentsOfSubscriberAsync(string subscriberId); + Task RemoveSubscribersAsync(long segmentId, RemoveSubscriberRequest request); + Task DeleteSegmentAsync(long segmentId); + } +} diff --git a/src/GregsStack.PushCrew.Net.Api/Request/RemoveSubscriberRequest.cs b/src/GregsStack.VwoEngage.Net.Api/Request/RemoveSubscriberRequest.cs similarity index 84% rename from src/GregsStack.PushCrew.Net.Api/Request/RemoveSubscriberRequest.cs rename to src/GregsStack.VwoEngage.Net.Api/Request/RemoveSubscriberRequest.cs index ee80648..bd56a38 100644 --- a/src/GregsStack.PushCrew.Net.Api/Request/RemoveSubscriberRequest.cs +++ b/src/GregsStack.VwoEngage.Net.Api/Request/RemoveSubscriberRequest.cs @@ -1,4 +1,4 @@ -namespace GregsStack.PushCrew.Net.Api.Request +namespace GregsStack.VwoEngage.Net.Api.Request { using System.Collections.Generic; diff --git a/src/GregsStack.PushCrew.Net.Api/Request/ScheduleMessageRequest.cs b/src/GregsStack.VwoEngage.Net.Api/Request/ScheduleMessageRequest.cs similarity index 98% rename from src/GregsStack.PushCrew.Net.Api/Request/ScheduleMessageRequest.cs rename to src/GregsStack.VwoEngage.Net.Api/Request/ScheduleMessageRequest.cs index cfeb8df..1154222 100644 --- a/src/GregsStack.PushCrew.Net.Api/Request/ScheduleMessageRequest.cs +++ b/src/GregsStack.VwoEngage.Net.Api/Request/ScheduleMessageRequest.cs @@ -1,4 +1,4 @@ -namespace GregsStack.PushCrew.Net.Api.Request +namespace GregsStack.VwoEngage.Net.Api.Request { using System; diff --git a/src/GregsStack.PushCrew.Net.Api/Request/SendMessageRequest.cs b/src/GregsStack.VwoEngage.Net.Api/Request/SendMessageRequest.cs similarity index 98% rename from src/GregsStack.PushCrew.Net.Api/Request/SendMessageRequest.cs rename to src/GregsStack.VwoEngage.Net.Api/Request/SendMessageRequest.cs index e5cfc56..e848a75 100644 --- a/src/GregsStack.PushCrew.Net.Api/Request/SendMessageRequest.cs +++ b/src/GregsStack.VwoEngage.Net.Api/Request/SendMessageRequest.cs @@ -1,4 +1,4 @@ -namespace GregsStack.PushCrew.Net.Api.Request +namespace GregsStack.VwoEngage.Net.Api.Request { using System; diff --git a/src/GregsStack.PushCrew.Net.Api/Request/SendMessageSubscriberRequest.cs b/src/GregsStack.VwoEngage.Net.Api/Request/SendMessageSubscriberRequest.cs similarity index 81% rename from src/GregsStack.PushCrew.Net.Api/Request/SendMessageSubscriberRequest.cs rename to src/GregsStack.VwoEngage.Net.Api/Request/SendMessageSubscriberRequest.cs index 9257ced..34683a7 100644 --- a/src/GregsStack.PushCrew.Net.Api/Request/SendMessageSubscriberRequest.cs +++ b/src/GregsStack.VwoEngage.Net.Api/Request/SendMessageSubscriberRequest.cs @@ -1,4 +1,4 @@ -namespace GregsStack.PushCrew.Net.Api.Request +namespace GregsStack.VwoEngage.Net.Api.Request { public class SendMessageSubscriberRequest : SendMessageRequest { diff --git a/src/GregsStack.PushCrew.Net.Api/Request/SendMessageSubscribersRequest.cs b/src/GregsStack.VwoEngage.Net.Api/Request/SendMessageSubscribersRequest.cs similarity index 82% rename from src/GregsStack.PushCrew.Net.Api/Request/SendMessageSubscribersRequest.cs rename to src/GregsStack.VwoEngage.Net.Api/Request/SendMessageSubscribersRequest.cs index d3f7ff2..f45a054 100644 --- a/src/GregsStack.PushCrew.Net.Api/Request/SendMessageSubscribersRequest.cs +++ b/src/GregsStack.VwoEngage.Net.Api/Request/SendMessageSubscribersRequest.cs @@ -1,4 +1,4 @@ -namespace GregsStack.PushCrew.Net.Api.Request +namespace GregsStack.VwoEngage.Net.Api.Request { public class SendMessageSubscribersRequest : SendMessageRequest { diff --git a/src/GregsStack.PushCrew.Net.Api/DevicePlatform.cs b/src/GregsStack.VwoEngage.Net.Api/Response/Models/DevicePlatform.cs similarity index 59% rename from src/GregsStack.PushCrew.Net.Api/DevicePlatform.cs rename to src/GregsStack.VwoEngage.Net.Api/Response/Models/DevicePlatform.cs index 883b761..3a5b8f5 100644 --- a/src/GregsStack.PushCrew.Net.Api/DevicePlatform.cs +++ b/src/GregsStack.VwoEngage.Net.Api/Response/Models/DevicePlatform.cs @@ -1,4 +1,4 @@ -namespace GregsStack.PushCrew.Net.Api +namespace GregsStack.VwoEngage.Net.Api.Response.Models { public enum DevicePlatform { diff --git a/src/GregsStack.PushCrew.Net.Api/Segment.cs b/src/GregsStack.VwoEngage.Net.Api/Response/Models/Segment.cs similarity index 71% rename from src/GregsStack.PushCrew.Net.Api/Segment.cs rename to src/GregsStack.VwoEngage.Net.Api/Response/Models/Segment.cs index a089af4..7bc94f5 100644 --- a/src/GregsStack.PushCrew.Net.Api/Segment.cs +++ b/src/GregsStack.VwoEngage.Net.Api/Response/Models/Segment.cs @@ -1,14 +1,14 @@ -namespace GregsStack.PushCrew.Net.Api +namespace GregsStack.VwoEngage.Net.Api.Response.Models { using System; public class Segment { - public string Id { get; set; } + public long Id { get; set; } public string Name { get; set; } - public Segment(string id, string name) + public Segment(long id, string name) { this.Id = id; this.Name = name ?? throw new ArgumentNullException(nameof(name)); diff --git a/src/GregsStack.PushCrew.Net.Api/Status.cs b/src/GregsStack.VwoEngage.Net.Api/Response/Models/Status.cs similarity index 50% rename from src/GregsStack.PushCrew.Net.Api/Status.cs rename to src/GregsStack.VwoEngage.Net.Api/Response/Models/Status.cs index c46a476..d0250bd 100644 --- a/src/GregsStack.PushCrew.Net.Api/Status.cs +++ b/src/GregsStack.VwoEngage.Net.Api/Response/Models/Status.cs @@ -1,7 +1,9 @@ -namespace GregsStack.PushCrew.Net.Api +namespace GregsStack.VwoEngage.Net.Api.Response.Models { public enum Status { + Unknown, + Success, Failure diff --git a/src/GregsStack.PushCrew.Net.Api/Subscriber.cs b/src/GregsStack.VwoEngage.Net.Api/Response/Models/Subscriber.cs similarity index 95% rename from src/GregsStack.PushCrew.Net.Api/Subscriber.cs rename to src/GregsStack.VwoEngage.Net.Api/Response/Models/Subscriber.cs index c869d17..87bb597 100644 --- a/src/GregsStack.PushCrew.Net.Api/Subscriber.cs +++ b/src/GregsStack.VwoEngage.Net.Api/Response/Models/Subscriber.cs @@ -1,4 +1,4 @@ -namespace GregsStack.PushCrew.Net.Api +namespace GregsStack.VwoEngage.Net.Api.Response.Models { using System; using System.Net; @@ -37,7 +37,7 @@ public class Subscriber public string Platform { get; set; } /// - /// Value can be / . + /// Value can be / . /// Depending upon the device from which the subscriber subscribed. /// public DevicePlatform DevicePlatform { get; set; } diff --git a/src/GregsStack.PushCrew.Net.Api/Response/NotificationStatusResponse.cs b/src/GregsStack.VwoEngage.Net.Api/Response/NotificationStatusResponse.cs similarity index 91% rename from src/GregsStack.PushCrew.Net.Api/Response/NotificationStatusResponse.cs rename to src/GregsStack.VwoEngage.Net.Api/Response/NotificationStatusResponse.cs index db66802..52f1413 100644 --- a/src/GregsStack.PushCrew.Net.Api/Response/NotificationStatusResponse.cs +++ b/src/GregsStack.VwoEngage.Net.Api/Response/NotificationStatusResponse.cs @@ -1,4 +1,4 @@ -namespace GregsStack.PushCrew.Net.Api.Response +namespace GregsStack.VwoEngage.Net.Api.Response { public class NotificationStatusResponse : StatusResponse { diff --git a/src/GregsStack.PushCrew.Net.Api/Response/ScheduleMessageResponse.cs b/src/GregsStack.VwoEngage.Net.Api/Response/ScheduleMessageResponse.cs similarity index 89% rename from src/GregsStack.PushCrew.Net.Api/Response/ScheduleMessageResponse.cs rename to src/GregsStack.VwoEngage.Net.Api/Response/ScheduleMessageResponse.cs index bc5734f..2d8c273 100644 --- a/src/GregsStack.PushCrew.Net.Api/Response/ScheduleMessageResponse.cs +++ b/src/GregsStack.VwoEngage.Net.Api/Response/ScheduleMessageResponse.cs @@ -1,4 +1,4 @@ -namespace GregsStack.PushCrew.Net.Api.Response +namespace GregsStack.VwoEngage.Net.Api.Response { public class ScheduleMessageResponse : StatusResponse { diff --git a/src/GregsStack.PushCrew.Net.Api/Response/SegmentResponse.cs b/src/GregsStack.VwoEngage.Net.Api/Response/SegmentResponse.cs similarity index 81% rename from src/GregsStack.PushCrew.Net.Api/Response/SegmentResponse.cs rename to src/GregsStack.VwoEngage.Net.Api/Response/SegmentResponse.cs index b7dd1ca..0268442 100644 --- a/src/GregsStack.PushCrew.Net.Api/Response/SegmentResponse.cs +++ b/src/GregsStack.VwoEngage.Net.Api/Response/SegmentResponse.cs @@ -1,4 +1,4 @@ -namespace GregsStack.PushCrew.Net.Api.Response +namespace GregsStack.VwoEngage.Net.Api.Response { public class SegmentResponse : StatusResponse { @@ -6,7 +6,7 @@ public class SegmentResponse : StatusResponse /// To identify the segment newly created. /// The same ID is used to add/remove subscribers to/from the segment through the API. /// - public string SegmentId { get; set; } + public long SegmentId { get; set; } /// /// Present in case of failure. Used to denote reason of failure. diff --git a/src/GregsStack.PushCrew.Net.Api/Response/SegmentsResponse.cs b/src/GregsStack.VwoEngage.Net.Api/Response/SegmentsResponse.cs similarity index 90% rename from src/GregsStack.PushCrew.Net.Api/Response/SegmentsResponse.cs rename to src/GregsStack.VwoEngage.Net.Api/Response/SegmentsResponse.cs index 1b8c618..f50973e 100644 --- a/src/GregsStack.PushCrew.Net.Api/Response/SegmentsResponse.cs +++ b/src/GregsStack.VwoEngage.Net.Api/Response/SegmentsResponse.cs @@ -1,7 +1,9 @@ -namespace GregsStack.PushCrew.Net.Api.Response +namespace GregsStack.VwoEngage.Net.Api.Response { using System.Collections.Generic; + using Models; + public class SegmentsResponse : StatusResponse { /// diff --git a/src/GregsStack.PushCrew.Net.Api/Response/SendMessageResponse.cs b/src/GregsStack.VwoEngage.Net.Api/Response/SendMessageResponse.cs similarity index 88% rename from src/GregsStack.PushCrew.Net.Api/Response/SendMessageResponse.cs rename to src/GregsStack.VwoEngage.Net.Api/Response/SendMessageResponse.cs index 9512ded..c0ce377 100644 --- a/src/GregsStack.PushCrew.Net.Api/Response/SendMessageResponse.cs +++ b/src/GregsStack.VwoEngage.Net.Api/Response/SendMessageResponse.cs @@ -1,4 +1,4 @@ -namespace GregsStack.PushCrew.Net.Api.Response +namespace GregsStack.VwoEngage.Net.Api.Response { public class SendMessageResponse : StatusResponse { diff --git a/src/GregsStack.PushCrew.Net.Api/Response/StatusResponse.cs b/src/GregsStack.VwoEngage.Net.Api/Response/StatusResponse.cs similarity index 56% rename from src/GregsStack.PushCrew.Net.Api/Response/StatusResponse.cs rename to src/GregsStack.VwoEngage.Net.Api/Response/StatusResponse.cs index b77a959..70c82ed 100644 --- a/src/GregsStack.PushCrew.Net.Api/Response/StatusResponse.cs +++ b/src/GregsStack.VwoEngage.Net.Api/Response/StatusResponse.cs @@ -1,9 +1,11 @@ -namespace GregsStack.PushCrew.Net.Api.Response +namespace GregsStack.VwoEngage.Net.Api.Response { + using Models; + public class StatusResponse { /// - /// To denote whether push request succeeded or not. Values can be or . + /// To denote whether push request succeeded or not. Values can be or . /// public Status Status { get; set; } } diff --git a/src/GregsStack.PushCrew.Net.Api/Response/SubscribersResponse.cs b/src/GregsStack.VwoEngage.Net.Api/Response/SubscribersResponse.cs similarity index 95% rename from src/GregsStack.PushCrew.Net.Api/Response/SubscribersResponse.cs rename to src/GregsStack.VwoEngage.Net.Api/Response/SubscribersResponse.cs index b4b93e7..a99aaba 100644 --- a/src/GregsStack.PushCrew.Net.Api/Response/SubscribersResponse.cs +++ b/src/GregsStack.VwoEngage.Net.Api/Response/SubscribersResponse.cs @@ -1,8 +1,10 @@ -namespace GregsStack.PushCrew.Net.Api.Response +namespace GregsStack.VwoEngage.Net.Api.Response { using System; using System.Collections.Generic; + using Models; + public class SubscribersResponse : StatusResponse { /// diff --git a/src/GregsStack.VwoEngage.Net.Api/VwoEngageClient.CheckStatus.cs b/src/GregsStack.VwoEngage.Net.Api/VwoEngageClient.CheckStatus.cs new file mode 100644 index 0000000..e3c0074 --- /dev/null +++ b/src/GregsStack.VwoEngage.Net.Api/VwoEngageClient.CheckStatus.cs @@ -0,0 +1,15 @@ +namespace GregsStack.VwoEngage.Net.Api +{ + using System.Threading.Tasks; + + using Response; + + public partial class VwoEngageClient + { + public async Task CheckNotificationRequestStatusAsync(string id) + { + var relativeUri = $"checkstatus/{id}"; + return await this.GetAsync(relativeUri); + } + } +} diff --git a/src/GregsStack.VwoEngage.Net.Api/VwoEngageClient.Segments.cs b/src/GregsStack.VwoEngage.Net.Api/VwoEngageClient.Segments.cs new file mode 100644 index 0000000..069abb4 --- /dev/null +++ b/src/GregsStack.VwoEngage.Net.Api/VwoEngageClient.Segments.cs @@ -0,0 +1,57 @@ +namespace GregsStack.VwoEngage.Net.Api +{ + using System; + using System.Collections.Generic; + using System.Threading.Tasks; + + using Newtonsoft.Json; + + using Request; + + using Response; + + public partial class VwoEngageClient + { + private const string SegmentsRelativeUri = "segments"; + + public async Task AddSegmentAsync(string name) + { + var dict = new Dictionary { { "name", name } }; + return await this.PostAsync, SegmentResponse>(dict, SegmentsRelativeUri); + } + + public async Task ListSegmentsAsync() => await this.GetAsync(SegmentsRelativeUri); + + public async Task AddSubscribersToSegmentAsync(long segmentId, ICollection subscriberList) + { + var relativeUri = $"{SegmentsRelativeUri}/{segmentId}/subscribers"; + + var validSubscriberList = subscriberList ?? throw new ArgumentNullException(nameof(subscriberList)); + var subscribers = new Dictionary> { { "subscriber_list", validSubscriberList } }; + var subscriberRequest = new SendMessageSubscribersRequest + { + SubscriberList = JsonConvert.SerializeObject(subscribers) + }; + + return await this.PostAsync(subscriberRequest, relativeUri); + } + + public async Task ListSubscribersInSegmentAsync(long segmentId, int page = 1, int perPage = 1024) + { + var relativeUri = $"{SegmentsRelativeUri}/{segmentId}/subscribers?page={page}&per_page={perPage}"; + return await this.GetAsync(relativeUri); + } + + public async Task RemoveSubscribersAsync(long segmentId, RemoveSubscriberRequest request) + { + var relativeUri = $"{SegmentsRelativeUri}/{segmentId}/subscribers"; + return await this.PutAsync(request, relativeUri); + } + + public async Task DeleteSegmentAsync(long segmentId) + { + var relativeUri = $"{SegmentsRelativeUri}/{segmentId}"; + return await this.DeleteAsync(relativeUri); + } + } +} diff --git a/src/GregsStack.VwoEngage.Net.Api/VwoEngageClient.Send.cs b/src/GregsStack.VwoEngage.Net.Api/VwoEngageClient.Send.cs new file mode 100644 index 0000000..f4838b5 --- /dev/null +++ b/src/GregsStack.VwoEngage.Net.Api/VwoEngageClient.Send.cs @@ -0,0 +1,66 @@ +namespace GregsStack.VwoEngage.Net.Api +{ + using System; + using System.Collections.Generic; + using System.Threading.Tasks; + + using Newtonsoft.Json; + + using Request; + + using Response; + + public partial class VwoEngageClient + { + private const string SendRelativeUri = "send"; + + public async Task SendAllSubscribersAsync(SendMessageRequest request) + { + var relativeUri = $"{SendRelativeUri}/all"; + return await this.PostAsync(request, relativeUri); + } + + public async Task SendSubscribersInSegmentAsync(long segmentId, SendMessageRequest request) + { + var relativeUri = $"{SendRelativeUri}/segment/{segmentId}"; + return await this.PostAsync(request, relativeUri); + } + + public async Task SendSubscribersAsync(ICollection subscriberList, SendMessageRequest request) + { + var relativeUri = $"{SendRelativeUri}/list"; + + var validRequest = request ?? throw new ArgumentNullException(nameof(request)); + var validSubscriberList = subscriberList ?? throw new ArgumentNullException(nameof(subscriberList)); + var subscribers = new Dictionary> { { "subscriber_list", validSubscriberList } }; + var subscriberRequest = (SendMessageSubscribersRequest)validRequest; + subscriberRequest.SubscriberList = JsonConvert.SerializeObject(subscribers); + + return await this.PostAsync(subscriberRequest, relativeUri); + } + + public async Task SendSubscriberAsync(string subscriberId, SendMessageRequest request) + { + var relativeUri = $"{SendRelativeUri}/individual"; + + var validRequest = request ?? throw new ArgumentNullException(nameof(request)); + var validSubscriberId = subscriberId ?? throw new ArgumentNullException(nameof(subscriberId)); + var subscriberRequest = (SendMessageSubscriberRequest)validRequest; + subscriberRequest.SubscriberId = validSubscriberId; + + return await this.PostAsync(subscriberRequest, relativeUri); + } + + public async Task ScheduleAllSubscribersAsync(ScheduleMessageRequest request) + { + var relativeUri = $"{SendRelativeUri}/all"; + return await this.PostAsync(request, relativeUri); + } + + public async Task ScheduleSegmentAsync(long segmentId, ScheduleMessageRequest request) + { + var relativeUri = $"{SendRelativeUri}/segment/{segmentId}"; + return await this.PostAsync(request, relativeUri); + } + } +} diff --git a/src/GregsStack.VwoEngage.Net.Api/VwoEngageClient.Subscribers.cs b/src/GregsStack.VwoEngage.Net.Api/VwoEngageClient.Subscribers.cs new file mode 100644 index 0000000..39b1aea --- /dev/null +++ b/src/GregsStack.VwoEngage.Net.Api/VwoEngageClient.Subscribers.cs @@ -0,0 +1,15 @@ +namespace GregsStack.VwoEngage.Net.Api +{ + using System.Threading.Tasks; + + using Response; + + public partial class VwoEngageClient + { + public async Task ListSegmentsOfSubscriberAsync(string subscriberId) + { + var relativeUri = $"subscribers/{subscriberId}/segments"; + return await this.GetAsync(relativeUri); + } + } +} diff --git a/src/GregsStack.VwoEngage.Net.Api/VwoEngageClient.cs b/src/GregsStack.VwoEngage.Net.Api/VwoEngageClient.cs new file mode 100644 index 0000000..dd26094 --- /dev/null +++ b/src/GregsStack.VwoEngage.Net.Api/VwoEngageClient.cs @@ -0,0 +1,104 @@ +namespace GregsStack.VwoEngage.Net.Api +{ + using System; + using System.Net; + using System.Net.Http; + using System.Net.Http.Formatting; + using System.Threading.Tasks; + + using Converters; + + using Exceptions; + + using Newtonsoft.Json.Converters; + using Newtonsoft.Json.Serialization; + + public partial class VwoEngageClient : IVwoEngageClient + { + private readonly IHttpClientFactory clientFactory; + private readonly JsonMediaTypeFormatter jsonFormatter; + + public VwoEngageClient(IHttpClientFactory httpClientFactory) + { + this.clientFactory = httpClientFactory ?? throw new ArgumentNullException(nameof(httpClientFactory)); + + var contractResolver = new DefaultContractResolver { NamingStrategy = new SnakeCaseNamingStrategy() }; + this.jsonFormatter = new JsonMediaTypeFormatter + { + SerializerSettings = + { + ContractResolver = contractResolver + } + }; + + this.jsonFormatter.SerializerSettings.Converters.Add(new StringEnumConverter()); + this.jsonFormatter.SerializerSettings.Converters.Add(new BooleanConverter()); + } + + private async Task DeleteAsync(string requestUri) + where TResponse : class => + await this.ExecuteAsync(httpClient => httpClient.DeleteAsync(requestUri)); + + private async Task GetAsync(string requestUri) + where TResponse : class => + await this.ExecuteAsync(httpClient => httpClient.GetAsync(requestUri)); + + private async Task PostAsync(TRequest request, string requestUri) + where TRequest : class + where TResponse : class => + await this.ExecuteAsync(httpClient => httpClient.PostAsync(requestUri, request.ToFormUrlEncodedContent())); + + private async Task PutAsync(TRequest request, string requestUri) + where TRequest : class + where TResponse : class => + await this.ExecuteAsync(httpClient => httpClient.PutAsync(requestUri, request, this.jsonFormatter)); + + private async Task ExecuteAsync(Func> requestAsync) + where TResponse : class + { + var client = this.CreateClient(); + using (var response = await requestAsync(client)) + { + await this.VerifyResponse(response); + return await this.ReadAsAsync(response); + } + } + + private HttpClient CreateClient() + { + return this.clientFactory.CreateClient(VwoEngageConfiguration.ClientName); + } + + private async Task VerifyResponse(HttpResponseMessage response) + { + switch (response.StatusCode) + { + case HttpStatusCode.Unauthorized: + { + var exception = await this.ReadAsAsync(response); + throw exception; + } + + case HttpStatusCode.BadRequest: + { + var exception = await this.ReadAsAsync(response); + throw exception; + } + + case HttpStatusCode.InternalServerError: + { + var exception = await this.ReadAsAsync(response); + throw exception; + } + } + + response.EnsureSuccessStatusCode(); + } + + private async Task ReadAsAsync(HttpResponseMessage response) + where TResponse : class + { + return await response.Content.ReadAsAsync(new[] { this.jsonFormatter }); + } + } +} diff --git a/src/GregsStack.VwoEngage.Net.Api/VwoEngageConfiguration.cs b/src/GregsStack.VwoEngage.Net.Api/VwoEngageConfiguration.cs new file mode 100644 index 0000000..0de1f55 --- /dev/null +++ b/src/GregsStack.VwoEngage.Net.Api/VwoEngageConfiguration.cs @@ -0,0 +1,11 @@ +namespace GregsStack.VwoEngage.Net.Api +{ + using System; + + public static class VwoEngageConfiguration + { + public static string ClientName { get; } = nameof(VwoEngageClient); + + public static Uri BaseUri { get; } = new Uri("https://pushcrew.com/api/v1/"); + } +} diff --git a/tests/GregsStack.PushCrew.Net.Api.Tests/ClientTests.cs b/tests/GregsStack.VwoEngage.Net.Api.Tests/ClientTests.cs similarity index 73% rename from tests/GregsStack.PushCrew.Net.Api.Tests/ClientTests.cs rename to tests/GregsStack.VwoEngage.Net.Api.Tests/ClientTests.cs index 3750689..5337b3a 100644 --- a/tests/GregsStack.PushCrew.Net.Api.Tests/ClientTests.cs +++ b/tests/GregsStack.VwoEngage.Net.Api.Tests/ClientTests.cs @@ -1,4 +1,4 @@ -namespace GregsStack.PushCrew.Net.Api.Tests +namespace GregsStack.VwoEngage.Net.Api.Tests { using Xunit; diff --git a/tests/GregsStack.PushCrew.Net.Api.Tests/Converters/FormUrlEncodedContentConverterTests.cs b/tests/GregsStack.VwoEngage.Net.Api.Tests/Converters/FormUrlEncodedContentConverterTests.cs similarity index 87% rename from tests/GregsStack.PushCrew.Net.Api.Tests/Converters/FormUrlEncodedContentConverterTests.cs rename to tests/GregsStack.VwoEngage.Net.Api.Tests/Converters/FormUrlEncodedContentConverterTests.cs index b689c43..6419f40 100644 --- a/tests/GregsStack.PushCrew.Net.Api.Tests/Converters/FormUrlEncodedContentConverterTests.cs +++ b/tests/GregsStack.VwoEngage.Net.Api.Tests/Converters/FormUrlEncodedContentConverterTests.cs @@ -1,4 +1,4 @@ -namespace GregsStack.PushCrew.Net.Api.Tests.Converters +namespace GregsStack.VwoEngage.Net.Api.Tests.Converters { using System; diff --git a/tests/GregsStack.PushCrew.Net.Api.Tests/GregsStack.PushCrew.Net.Api.Tests.csproj b/tests/GregsStack.VwoEngage.Net.Api.Tests/GregsStack.VwoEngage.Net.Api.Tests.csproj similarity index 63% rename from tests/GregsStack.PushCrew.Net.Api.Tests/GregsStack.PushCrew.Net.Api.Tests.csproj rename to tests/GregsStack.VwoEngage.Net.Api.Tests/GregsStack.VwoEngage.Net.Api.Tests.csproj index 83ac071..ce16744 100644 --- a/tests/GregsStack.PushCrew.Net.Api.Tests/GregsStack.PushCrew.Net.Api.Tests.csproj +++ b/tests/GregsStack.VwoEngage.Net.Api.Tests/GregsStack.VwoEngage.Net.Api.Tests.csproj @@ -1,12 +1,14 @@  - netcoreapp3.0 + netcoreapp2.2 false + + @@ -16,7 +18,7 @@ - + diff --git a/tests/GregsStack.VwoEngage.Net.Api.Tests/Sample.cs b/tests/GregsStack.VwoEngage.Net.Api.Tests/Sample.cs new file mode 100644 index 0000000..cd91125 --- /dev/null +++ b/tests/GregsStack.VwoEngage.Net.Api.Tests/Sample.cs @@ -0,0 +1,29 @@ +namespace GregsStack.VwoEngage.Net.Api.Tests +{ + using System.Threading.Tasks; + + using Exceptions; + + using Extensions; + + using Microsoft.Extensions.DependencyInjection; + + using Xunit; + + public class Sample + { + [Fact] + public async Task AnyMethod_BadToken_ThrowsUnauthorizedException() + { + var di = new ServiceCollection(); + + di.AddPushCrewHttpClient("BadToken"); + di.AddTransient(); + + var serviceProvider = di.BuildServiceProvider(); + + var client = serviceProvider.GetRequiredService(); + await Assert.ThrowsAsync(() => client.DeleteSegmentAsync(123)); + } + } +} diff --git a/tools/GregsStack.NuGet.Tools/GregsStack.NuGet.Tools.csproj b/tools/GregsStack.NuGet.Tools/GregsStack.NuGet.Tools.csproj index 6fabe92..4293885 100644 --- a/tools/GregsStack.NuGet.Tools/GregsStack.NuGet.Tools.csproj +++ b/tools/GregsStack.NuGet.Tools/GregsStack.NuGet.Tools.csproj @@ -6,7 +6,7 @@ - +