зеркало из https://github.com/Azure/Sia-Gateway.git
Merged from master
This commit is contained in:
Коммит
d9ba93e9cc
|
@ -1,7 +1,7 @@
|
|||
|
||||
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||
# Visual Studio 15
|
||||
VisualStudioVersion = 15.0.26730.12
|
||||
VisualStudioVersion = 15.0.26730.16
|
||||
MinimumVisualStudioVersion = 10.0.40219.1
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Sia.Shared", "src\Sia.Shared\Sia.Shared.csproj", "{C4AA9F6D-DFB1-4044-A7CD-FA3B6D5BA6AE}"
|
||||
EndProject
|
||||
|
@ -21,6 +21,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "test", "test", "{931F26AB-5
|
|||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Sia.Gateway.Tests", "test\Sia.Gateway.Tests\Sia.Gateway.Tests.csproj", "{7FED8930-1B06-4954-8337-C2DEC6697D6F}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Sia.Shared.Tests", "Sia.Shared.Tests\Sia.Shared.Tests.csproj", "{BF2EE536-9EB2-4E97-8F6A-BDF6571BAA67}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Sia.Data.Playbooks", "Sia.Data.Playbooks\Sia.Data.Playbooks.csproj", "{8223434F-1377-4793-869A-ADF9EE2F08AF}"
|
||||
EndProject
|
||||
Global
|
||||
|
@ -57,6 +59,10 @@ Global
|
|||
{7FED8930-1B06-4954-8337-C2DEC6697D6F}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{7FED8930-1B06-4954-8337-C2DEC6697D6F}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{7FED8930-1B06-4954-8337-C2DEC6697D6F}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{BF2EE536-9EB2-4E97-8F6A-BDF6571BAA67}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{BF2EE536-9EB2-4E97-8F6A-BDF6571BAA67}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{BF2EE536-9EB2-4E97-8F6A-BDF6571BAA67}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{BF2EE536-9EB2-4E97-8F6A-BDF6571BAA67}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{8223434F-1377-4793-869A-ADF9EE2F08AF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{8223434F-1377-4793-869A-ADF9EE2F08AF}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{8223434F-1377-4793-869A-ADF9EE2F08AF}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
|
@ -73,6 +79,7 @@ Global
|
|||
{B92790C5-C4F1-4052-ABA7-68448F28DDB2} = {09E441A6-06F8-457A-981A-394473FB8CD5}
|
||||
{8D465104-1385-4EE4-A1ED-AEE7708A537F} = {09E441A6-06F8-457A-981A-394473FB8CD5}
|
||||
{7FED8930-1B06-4954-8337-C2DEC6697D6F} = {931F26AB-58CD-4667-A281-EEC186F2FB59}
|
||||
{BF2EE536-9EB2-4E97-8F6A-BDF6571BAA67} = {931F26AB-58CD-4667-A281-EEC186F2FB59}
|
||||
{8223434F-1377-4793-869A-ADF9EE2F08AF} = {09E441A6-06F8-457A-981A-394473FB8CD5}
|
||||
EndGlobalSection
|
||||
GlobalSection(ExtensibilityGlobals) = postSolution
|
||||
|
|
|
@ -0,0 +1,72 @@
|
|||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using Sia.Shared.Data;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
|
||||
namespace Sia.Shared.Tests.Data
|
||||
{
|
||||
[TestClass]
|
||||
public class PartialJsonResolverTests
|
||||
{
|
||||
[TestMethod]
|
||||
public void ResolveJsonToString_Method_Resolve_SerializesSourceArgumentObjectToString()
|
||||
{
|
||||
var input = new TestHasJsonDataObject
|
||||
{
|
||||
Data = new JsonSerializationTestObject()
|
||||
};
|
||||
var expectedResultDataValue = JsonSerializationTestObject.ExpectedSerialization();
|
||||
var objectUnderTest = new ResolveJsonToString<TestHasJsonDataObject, TestHasJsonDataString>();
|
||||
|
||||
|
||||
var result = objectUnderTest.Resolve(input, null, null, null);
|
||||
|
||||
|
||||
Assert.AreEqual(expectedResultDataValue, result, false);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void ResolveStringToJson_Method_Resolve_SerializesSourceArgumentStringToObject()
|
||||
{
|
||||
var input = new TestHasJsonDataString()
|
||||
{
|
||||
Data = JsonSerializationTestObject.ExpectedSerialization()
|
||||
};
|
||||
var expectedResult = new JsonSerializationTestObject();
|
||||
var objectUnderTest = new ResolveStringToJson<TestHasJsonDataString, TestHasJsonDataObject>();
|
||||
|
||||
|
||||
var result = objectUnderTest.Resolve(input, null, null, null);
|
||||
|
||||
|
||||
Assert.AreEqual(expectedResult.a, ExtractPropertyFromResult(result, "a"));
|
||||
Assert.AreEqual(expectedResult.b, ExtractPropertyFromResult(result, "b"));
|
||||
}
|
||||
|
||||
private static JToken ExtractPropertyFromResult(object result, string propName) => ((JObject)result).Property(propName).Value;
|
||||
}
|
||||
|
||||
internal class JsonSerializationTestObject :IEquatable<JsonSerializationTestObject>
|
||||
{
|
||||
public static string ExpectedSerialization()
|
||||
=> "{\"a\":\"ValueOfA\",\"b\":1}";
|
||||
public bool Equals(JsonSerializationTestObject other)
|
||||
=> a == other.a && b == other.b;
|
||||
|
||||
public string a { get; set; } = "ValueOfA";
|
||||
public int b { get; set; } = 1;
|
||||
}
|
||||
|
||||
internal class TestHasJsonDataString : IJsonDataString
|
||||
{
|
||||
public string Data { get; set; }
|
||||
}
|
||||
|
||||
internal class TestHasJsonDataObject : IJsonDataObject
|
||||
{
|
||||
public object Data { get; set; }
|
||||
}
|
||||
}
|
|
@ -0,0 +1,17 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>netcoreapp2.0</TargetFramework>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="15.5.0-preview-20171012-09" />
|
||||
<PackageReference Include="MSTest.TestAdapter" Version="1.2.0" />
|
||||
<PackageReference Include="MSTest.TestFramework" Version="1.2.0" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\src\Sia.Shared\Sia.Shared.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
|
@ -0,0 +1,65 @@
|
|||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
using Sia.Shared.Validation;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
|
||||
namespace Sia.Shared.Tests.Validation
|
||||
{
|
||||
[TestClass]
|
||||
public class ThrowIfTests
|
||||
{
|
||||
[TestMethod]
|
||||
public void Null_StaticMethod_WhenObjectIsNotNull_ReturnsObject()
|
||||
{
|
||||
var input = new Object();
|
||||
|
||||
var result = ThrowIf.Null(input, nameof(input));
|
||||
|
||||
Assert.AreSame(input, result);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
[ExpectedException(typeof(ArgumentNullException))]
|
||||
public void Null_StaticMethod_WhenObjectIsNull_ThrowsArgumentNullException()
|
||||
{
|
||||
object input = null;
|
||||
|
||||
var result = ThrowIf.Null(input, nameof(input));
|
||||
|
||||
//expect exception
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
[ExpectedException(typeof(ArgumentException))]
|
||||
public void NullOrWhiteSpace_StaticMethod_WhenInputIsNull_ThrowsArgumentException()
|
||||
{
|
||||
string input = null;
|
||||
|
||||
var result = ThrowIf.NullOrWhiteSpace(input, nameof(input));
|
||||
|
||||
//Expect exception
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
[ExpectedException(typeof(ArgumentException))]
|
||||
public void NullOrWhiteSpace_StaticMethod_WhenInputIsOnlyWhitespace_ThrowsArgumentException()
|
||||
{
|
||||
string input = " ";
|
||||
|
||||
var result = ThrowIf.NullOrWhiteSpace(input, nameof(input));
|
||||
|
||||
//Expect exception
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void NullOrWhiteSpace_StaticMethod_WhenInputStringWithAnyNonWhitespace_ReturnsString()
|
||||
{
|
||||
string input = " . ";
|
||||
|
||||
var result = ThrowIf.NullOrWhiteSpace(input, nameof(input));
|
||||
|
||||
Assert.AreSame(input, result);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -3,7 +3,7 @@
|
|||
<PropertyGroup>
|
||||
<TargetFramework>netcoreapp2.0</TargetFramework>
|
||||
<PackageId>Microsoft.Sia.Connectors.Tickets</PackageId>
|
||||
<Version>1.0.5-alpha</Version>
|
||||
<Version>1.0.6-alpha</Version>
|
||||
<Authors>pdimit, magpint, jache, chtownes</Authors>
|
||||
<Company>Microsoft</Company>
|
||||
<Product>SRE Incident Assistant</Product>
|
||||
|
@ -15,13 +15,13 @@
|
|||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="AutoMapper" Version="6.1.1" />
|
||||
<PackageReference Include="Microsoft.Sia.Shared" Version="1.0.7-alpha" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\Sia.Data.Incident\Sia.Data.Incidents.csproj" />
|
||||
<ProjectReference Include="..\Sia.Domain.ApiModels\Sia.Domain.ApiModels.csproj" />
|
||||
<ProjectReference Include="..\Sia.Domain\Sia.Domain.csproj" />
|
||||
<ProjectReference Include="..\Sia.Shared\Sia.Shared.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
|
@ -3,6 +3,7 @@
|
|||
public enum AuthenticationType
|
||||
{
|
||||
None,
|
||||
Certificate
|
||||
Certificate,
|
||||
CertificateFromKeyVault
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Sia.Connectors.Tickets;
|
||||
using Sia.Connectors.Tickets.TicketProxy;
|
||||
using Sia.Shared.Authentication;
|
||||
|
||||
namespace Sia.Gateway.Initialization
|
||||
{
|
||||
|
@ -12,14 +14,17 @@ namespace Sia.Gateway.Initialization
|
|||
|
||||
public static IServiceCollection AddProxyWithCert(this IServiceCollection services, string endpoint, string certThumbprint)
|
||||
=> services.AddProxy(new ProxyConnectionInfo(endpoint, certThumbprint));
|
||||
|
||||
|
||||
|
||||
public static IServiceCollection AddProxyWithCertFromKeyVault(this IServiceCollection services, string endpoint, KeyVaultConfiguration config, string certName)
|
||||
=> services.AddProxy(new ProxyConnectionInfo(endpoint, config, certName));
|
||||
|
||||
private static IServiceCollection AddProxy(this IServiceCollection services, ProxyConnectionInfo proxyConnection)
|
||||
{
|
||||
return services
|
||||
.AddScoped<Converter<Ticket>, ProxyConverter>()
|
||||
.AddScoped<Client<Ticket>>(serv => proxyConnection.GetClient())
|
||||
.AddScoped<Connector<Ticket>, ProxyConnector>();
|
||||
.AddScoped<Converter<ProxyTicket>, ProxyConverter>()
|
||||
.AddScoped<Client<ProxyTicket>>(serv => proxyConnection.GetClient())
|
||||
.AddScoped<Connector<ProxyTicket>, ProxyConnector>();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,7 +4,7 @@ using System.Threading.Tasks;
|
|||
|
||||
namespace Sia.Connectors.Tickets.TicketProxy
|
||||
{
|
||||
public class ProxyClient : Client<Ticket>
|
||||
public class ProxyClient : Client<ProxyTicket>
|
||||
{
|
||||
private readonly string _endpoint;
|
||||
private readonly HttpClient _client;
|
||||
|
@ -15,12 +15,12 @@ namespace Sia.Connectors.Tickets.TicketProxy
|
|||
_client = singletonClient;
|
||||
}
|
||||
|
||||
public override async Task<Ticket> GetAsync(string originId)
|
||||
public override async Task<ProxyTicket> GetAsync(string originId)
|
||||
{
|
||||
string incidentUrl = $"{_endpoint}/{originId}";
|
||||
var response = await _client.GetAsync(incidentUrl);
|
||||
var content = await response.Content.ReadAsStringAsync();
|
||||
return JsonConvert.DeserializeObject<Ticket>(content);
|
||||
return JsonConvert.DeserializeObject<ProxyTicket>(content);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
using Sia.Shared.Authentication;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Sia.Shared.Authentication;
|
||||
using Sia.Shared.Validation;
|
||||
using System;
|
||||
|
||||
|
@ -6,14 +7,38 @@ namespace Sia.Connectors.Tickets.TicketProxy
|
|||
{
|
||||
public class ProxyConnectionInfo
|
||||
{
|
||||
private AzureSecretVault _keyVault;
|
||||
|
||||
/// <summary>
|
||||
/// Instantiates ProxyConnectionInfo with no Authentication
|
||||
/// </summary>
|
||||
/// <param name="endpoint">Proxy Endpoint</param>
|
||||
public ProxyConnectionInfo(string endpoint)
|
||||
: this(endpoint, AuthenticationType.None)
|
||||
{ }
|
||||
|
||||
/// <summary>
|
||||
/// Instantiates ProxyConnectionInfo with certificate authentication from a local cert
|
||||
/// </summary>
|
||||
/// <param name="endpoint">Proxy Endpoint</param>
|
||||
/// <param name="certThumbprint">Thumbprint for searching local certificate store</param>
|
||||
public ProxyConnectionInfo(string endpoint, string certThumbprint)
|
||||
: this(endpoint, AuthenticationType.Certificate)
|
||||
{
|
||||
CertThumbprint = ThrowIf.NullOrWhiteSpace(certThumbprint, nameof(certThumbprint));
|
||||
CertIdentifier = ThrowIf.NullOrWhiteSpace(certThumbprint, nameof(certThumbprint));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Instantiates ProxyConnectionInfo with certificate authentication using a certificate retrieved from keyvault
|
||||
/// </summary>
|
||||
/// <param name="endpoint">Proxy Endpoint</param>
|
||||
/// <param name="config">Configuration root for initialization</param>
|
||||
/// <param name="vaultName">Key vault name</param>
|
||||
public ProxyConnectionInfo(string endpoint, KeyVaultConfiguration config, string vaultName)
|
||||
: this(endpoint, AuthenticationType.CertificateFromKeyVault)
|
||||
{
|
||||
_keyVault = new AzureSecretVault(config);
|
||||
CertIdentifier = ThrowIf.NullOrWhiteSpace(vaultName, nameof(vaultName));
|
||||
}
|
||||
|
||||
protected ProxyConnectionInfo(string endpoint, AuthenticationType authType)
|
||||
|
@ -23,9 +48,9 @@ namespace Sia.Connectors.Tickets.TicketProxy
|
|||
}
|
||||
|
||||
public ProxyClient GetClient() => new ProxyClient(ClientFactory.GetClient(), Endpoint);
|
||||
public AuthenticationType AuthenticationType { get; set; }
|
||||
public string Endpoint { get; set; }
|
||||
public string CertThumbprint { get; set; }
|
||||
public AuthenticationType AuthenticationType { get; protected set; }
|
||||
public string Endpoint { get; protected set; }
|
||||
public string CertIdentifier { get; protected set; }
|
||||
|
||||
protected IHttpClientFactory ClientFactory
|
||||
{
|
||||
|
@ -34,7 +59,9 @@ namespace Sia.Connectors.Tickets.TicketProxy
|
|||
switch (AuthenticationType)
|
||||
{
|
||||
case AuthenticationType.Certificate:
|
||||
return new LocalCertificateRetriever(CertThumbprint);
|
||||
return new LocalCertificateRetriever(CertIdentifier);
|
||||
case AuthenticationType.CertificateFromKeyVault:
|
||||
return new KeyVaultCertificateRetriever(_keyVault, CertIdentifier);
|
||||
case AuthenticationType.None:
|
||||
return new UnauthenticatedClientFactory();
|
||||
default:
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
namespace Sia.Connectors.Tickets.TicketProxy
|
||||
{
|
||||
public class ProxyConnector : Connector<Ticket>
|
||||
public class ProxyConnector : Connector<ProxyTicket>
|
||||
{
|
||||
public ProxyConnector(Client<Ticket> client, Converter<Ticket> converter)
|
||||
public ProxyConnector(Client<ProxyTicket> client, Converter<ProxyTicket> converter)
|
||||
: base(client, converter)
|
||||
{
|
||||
}
|
||||
|
|
|
@ -3,9 +3,9 @@ using System.Collections.Generic;
|
|||
|
||||
namespace Sia.Connectors.Tickets.TicketProxy
|
||||
{
|
||||
public class ProxyConverter : Converter<Ticket>
|
||||
public class ProxyConverter : Converter<ProxyTicket>
|
||||
{
|
||||
public override ICollection<Event> ExtractEvents(Ticket ticket)
|
||||
public override ICollection<Event> ExtractEvents(ProxyTicket ticket)
|
||||
{
|
||||
return new List<Event>();
|
||||
}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
namespace Sia.Connectors.Tickets.TicketProxy
|
||||
{
|
||||
public class Ticket
|
||||
public class ProxyTicket
|
||||
{
|
||||
public string OriginId { get; set; }
|
||||
public long IncidentSystemId { get; set; }
|
||||
|
|
|
@ -1,14 +1,18 @@
|
|||
using Sia.Shared.Data;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Sia.Shared.Data;
|
||||
using System;
|
||||
using System.ComponentModel.DataAnnotations.Schema;
|
||||
|
||||
namespace Sia.Data.Incidents.Models
|
||||
{
|
||||
public class Event : IEntity
|
||||
public class Event : IEntity, IJsonDataString
|
||||
{
|
||||
public long Id { get; set; }
|
||||
public long? IncidentId { get; set; }
|
||||
public long EventTypeId { get; set; }
|
||||
public DateTime Occurred { get; set; }
|
||||
public DateTime EventFired { get; set; }
|
||||
[Column(TypeName = "varchar(max)")]
|
||||
public string Data { get; set; }
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
using Sia.Data.Incidents.Models;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace Sia.Data.Incidents
|
||||
{
|
||||
|
@ -11,8 +12,10 @@ namespace Sia.Data.Incidents
|
|||
const int differentEventTypes = 8;
|
||||
const int eventCountForManyEvents = 1000;
|
||||
//Some dev/test/demo data that was based on actual incidents has been [REDACTED]
|
||||
public static void Add(IncidentContext incidentContext, SeedType seedtype)
|
||||
public static void Add(IncidentContext incidentContext, SeedType seedtype = SeedType.Basic)
|
||||
{
|
||||
if (incidentContext.Incidents.Any()) return; //This context already has seed data loaded
|
||||
|
||||
var firstTestIncidentSystem = new TicketingSystem
|
||||
{
|
||||
Name = "Not Our Ticketing System Name"
|
||||
|
|
|
@ -16,10 +16,13 @@
|
|||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" Version="2.0.0" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="2.0.0" />
|
||||
<PackageReference Include="Microsoft.Sia.Shared" Version="1.0.7-alpha" />
|
||||
<PackageReference Include="System.Data.SqlClient" Version="4.4.0" />
|
||||
<PackageReference Include="System.Globalization.Extensions" Version="4.3.0" />
|
||||
<DotNetCliToolReference Include="Microsoft.EntityFrameworkCore.Tools.DotNet" Version="2.0.0" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\Sia.Shared\Sia.Shared.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
|
@ -1,9 +1,12 @@
|
|||
using System;
|
||||
using Sia.Shared.Data;
|
||||
using System;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.Dynamic;
|
||||
|
||||
namespace Sia.Domain.ApiModels
|
||||
{
|
||||
public class NewEvent
|
||||
:IJsonDataObject
|
||||
{
|
||||
[Required]
|
||||
public long? EventTypeId { get; set; }
|
||||
|
@ -11,5 +14,6 @@ namespace Sia.Domain.ApiModels
|
|||
public DateTime? Occurred { get; set; }
|
||||
[Required]
|
||||
public DateTime? EventFired { get; set; }
|
||||
public object Data { get; set; }
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
using System.Collections.Generic;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.Linq;
|
||||
|
||||
namespace Sia.Domain.ApiModels
|
||||
{
|
||||
|
@ -8,7 +9,26 @@ namespace Sia.Domain.ApiModels
|
|||
[Required]
|
||||
public string Title { get; set; }
|
||||
[Required]
|
||||
public Ticket PrimaryTicket { get; set; }
|
||||
public Ticket PrimaryTicket
|
||||
{
|
||||
get
|
||||
{
|
||||
return Tickets.FirstOrDefault(ticket => ticket.IsPrimary);
|
||||
}
|
||||
set
|
||||
{
|
||||
if (Tickets == null) Tickets = new List<Ticket>();
|
||||
foreach (var ticket in Tickets.Where(ticket => ticket.IsPrimary))
|
||||
{
|
||||
ticket.IsPrimary = false;
|
||||
}
|
||||
|
||||
if (value == null) return;
|
||||
|
||||
if (!Tickets.Contains(value)) Tickets.Add(value);
|
||||
value.IsPrimary = true;
|
||||
}
|
||||
}
|
||||
public IList<Ticket> Tickets { get; set; }
|
||||
= new List<Ticket>();
|
||||
public IList<NewEvent> Events { get; set; }
|
||||
|
|
|
@ -20,6 +20,7 @@
|
|||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\Sia.Domain\Sia.Domain.csproj" />
|
||||
<ProjectReference Include="..\Sia.Shared\Sia.Shared.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
|
@ -2,16 +2,18 @@
|
|||
using Sia.Shared.Data;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Dynamic;
|
||||
using System.Text;
|
||||
|
||||
namespace Sia.Domain
|
||||
{
|
||||
public class Event : IEntity
|
||||
public class Event : IEntity, IJsonDataObject
|
||||
{
|
||||
public long Id { get; set; }
|
||||
public long? IncidentId { get; set; }
|
||||
public long EventTypeId { get; set; }
|
||||
public DateTime Occurred { get; set; }
|
||||
public DateTime EventFired { get; set; }
|
||||
public object Data { get; set; }
|
||||
}
|
||||
}
|
||||
|
|
|
@ -8,7 +8,7 @@ using Sia.Gateway.Validation.Filters;
|
|||
namespace Sia.Gateway.Controllers
|
||||
{
|
||||
[Return400BadRequestWhenModelStateInvalid]
|
||||
//[Authorize()]
|
||||
[Authorize()]
|
||||
public abstract class BaseController : Controller
|
||||
{
|
||||
protected readonly IMediator _mediator;
|
||||
|
|
|
@ -36,7 +36,7 @@ namespace Sia.Gateway.Controllers
|
|||
{
|
||||
return NotFound(notFoundMessage);
|
||||
}
|
||||
return Created($"api/incidents/{result.IncidentId}/engagements/{result.Id}", result);
|
||||
return Created($"incidents/{result.IncidentId}/engagements/{result.Id}", result);
|
||||
}
|
||||
|
||||
[HttpPut("{engagementId}")]
|
||||
|
|
|
@ -1,8 +1,10 @@
|
|||
using MediatR;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.AspNetCore.SignalR.Client;
|
||||
using Sia.Domain.ApiModels;
|
||||
using Sia.Gateway.Authentication;
|
||||
using Sia.Gateway.Protocol;
|
||||
using Sia.Gateway.Hubs;
|
||||
using Sia.Gateway.Requests;
|
||||
using Sia.Gateway.Requests.Events;
|
||||
using System.Threading.Tasks;
|
||||
|
@ -13,17 +15,22 @@ namespace Sia.Gateway.Controllers
|
|||
public class EventsController : BaseController
|
||||
{
|
||||
private const string notFoundMessage = "Incident or event not found";
|
||||
private readonly HubConnectionBuilder _hubConnectionBuilder;
|
||||
|
||||
public EventsController(IMediator mediator, AzureActiveDirectoryAuthenticationInfo authConfig, IUrlHelper urlHelper)
|
||||
public EventsController(IMediator mediator,
|
||||
AzureActiveDirectoryAuthenticationInfo authConfig,
|
||||
HubConnectionBuilder hubConnectionBuilder,
|
||||
IUrlHelper urlHelper)
|
||||
: base(mediator, authConfig, urlHelper)
|
||||
{
|
||||
_hubConnectionBuilder = hubConnectionBuilder;
|
||||
}
|
||||
|
||||
[HttpGet(Name = nameof(GetEvents))]
|
||||
public async Task<IActionResult> GetEvents([FromRoute]long incidentId, [FromQuery]PaginationMetadata pagination)
|
||||
{
|
||||
var result = await _mediator.Send(new GetEventsRequest(incidentId, pagination, _authContext));
|
||||
Response.Headers.AddPagination(new PaginationHeader(pagination, _urlHelper, nameof(GetEvents)));
|
||||
Response.Headers.AddPagination(new LinksHeader(pagination, _urlHelper, nameof(GetEvents)));
|
||||
return Ok(result);
|
||||
}
|
||||
|
||||
|
@ -47,7 +54,18 @@ namespace Sia.Gateway.Controllers
|
|||
{
|
||||
return NotFound(notFoundMessage);
|
||||
}
|
||||
await SendEventToSubscribers(result);
|
||||
return Created($"api/incidents/{result.IncidentId}/events/{result.Id}", result);
|
||||
}
|
||||
|
||||
private async Task SendEventToSubscribers(Domain.Event result)
|
||||
{
|
||||
var eventHubConnection = _hubConnectionBuilder
|
||||
.WithUrl($"{Request.Scheme}://{Request.Host}/{EventsHub.HubPath}")
|
||||
.Build();
|
||||
await eventHubConnection.StartAsync();
|
||||
await eventHubConnection.SendAsync("Send", result);
|
||||
await eventHubConnection.DisposeAsync();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,8 +1,10 @@
|
|||
using MediatR;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Sia.Domain;
|
||||
using Sia.Domain.ApiModels;
|
||||
using Sia.Gateway.Authentication;
|
||||
using Sia.Gateway.Requests;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
|
||||
|
@ -19,11 +21,7 @@ namespace Sia.Gateway.Controllers
|
|||
[HttpGet("{id}")]
|
||||
public async Task<IActionResult> Get(string id)
|
||||
{
|
||||
var result = await _mediator.Send(new GetIncidentsByTicketRequest(id, _authContext));
|
||||
if (result == null)
|
||||
{
|
||||
return NotFound($"{nameof(Incident)} not found");
|
||||
}
|
||||
var result = await _mediator.Send(new GetIncidentsByTicketCreateIfNeededRequest(id, _authContext));
|
||||
return Ok(result);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,30 @@
|
|||
using Microsoft.AspNetCore.SignalR;
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Serialization;
|
||||
using Sia.Domain;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Sia.Gateway.Hubs
|
||||
{
|
||||
public class EventsHub : Hub
|
||||
{
|
||||
public const string HubPath = "events/live";
|
||||
public EventsHub() : base()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public Task Send(Event ev)
|
||||
{
|
||||
return Clients.All.InvokeAsync("Send", Json(ev));
|
||||
}
|
||||
|
||||
private string Json<T>(T toSerialize) => JsonConvert.SerializeObject(toSerialize, new JsonSerializerSettings
|
||||
{
|
||||
ContractResolver = new CamelCasePropertyNamesContractResolver()
|
||||
});
|
||||
}
|
||||
}
|
|
@ -0,0 +1,32 @@
|
|||
using Microsoft.AspNetCore.Hosting;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Sia.Shared.Authentication;
|
||||
|
||||
namespace Sia.Gateway.Initialization
|
||||
{
|
||||
public static class ApplicationInsightsStartup
|
||||
{
|
||||
public static AzureSecretVault InitializeApplicationInsights(this IHostingEnvironment env, IConfigurationRoot configuration)
|
||||
{
|
||||
//Needs to be done in the initial Startup.Startup() method because Application Insights registers itself prior
|
||||
//to ConfigureServices being run
|
||||
var secrets = new AzureSecretVault(
|
||||
new KeyVaultConfiguration(
|
||||
configuration["ClientId"],
|
||||
configuration["ClientSecret"],
|
||||
configuration["KeyVault:VaultName"]
|
||||
)
|
||||
);
|
||||
|
||||
var instrumentationKey = configuration.GetSection("KeyVault")["InstrumentationKeyName"];
|
||||
if (!string.IsNullOrWhiteSpace(instrumentationKey))
|
||||
{
|
||||
var vaultTask = secrets.Get(instrumentationKey);
|
||||
vaultTask.Wait();
|
||||
configuration.GetSection("ApplicationInsights")["InstrumentationKey"] = vaultTask.Result;
|
||||
}
|
||||
|
||||
return secrets;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -2,6 +2,7 @@
|
|||
using AutoMapper.EquivalencyExpression;
|
||||
using Sia.Domain;
|
||||
using Sia.Domain.ApiModels;
|
||||
using Sia.Gateway.Protocol;
|
||||
using Sia.Shared.Data;
|
||||
|
||||
namespace Sia.Gateway.Initialization
|
||||
|
@ -32,25 +33,35 @@ namespace Sia.Gateway.Initialization
|
|||
|
||||
configuration.CreateMap<Participant, Data.Incidents.Models.Participant>();
|
||||
configuration.CreateMap<Data.Incidents.Models.Participant, Participant>();
|
||||
|
||||
configuration.CreateMap<NewEvent, Data.Incidents.Models.Event>().EqualityInsertOnly();
|
||||
configuration.CreateMap<Event, Data.Incidents.Models.Event>().EqualityById();
|
||||
configuration.CreateMap<Data.Incidents.Models.Event, Event>().EqualityById();
|
||||
configuration.CreateMap<NewEvent, Data.Incidents.Models.Event>().EqualityInsertOnly()
|
||||
.UseResolveJsonToString();
|
||||
configuration.CreateMap<Event, Data.Incidents.Models.Event>().EqualityById()
|
||||
.UseResolveJsonToString();
|
||||
configuration.CreateMap<Data.Incidents.Models.Event, Event>().EqualityById()
|
||||
.UseResolveStringToJson();
|
||||
});
|
||||
}
|
||||
|
||||
private static IMappingExpression<TSource, TDestination> UseResolveJsonToString<TSource, TDestination>(this IMappingExpression<TSource, TDestination> mapping)
|
||||
where TSource: IJsonDataObject
|
||||
where TDestination: IJsonDataString
|
||||
=> mapping.ForMember((ev) => ev.Data, (config) => config.ResolveUsing<ResolveJsonToString<TSource, TDestination>>());
|
||||
|
||||
|
||||
private static IMappingExpression<TSource, TDestination> UseResolveStringToJson<TSource, TDestination>(this IMappingExpression<TSource, TDestination> mapping)
|
||||
where TSource : IJsonDataString
|
||||
where TDestination : IJsonDataObject
|
||||
=> mapping.ForMember((ev) => ev.Data, (config) => config.ResolveUsing<ResolveStringToJson<TSource, TDestination>>());
|
||||
|
||||
|
||||
public static IMappingExpression<T1, T2> EqualityInsertOnly<T1, T2>(this IMappingExpression<T1, T2> mappingExpression)
|
||||
where T1 : class
|
||||
where T2 : class
|
||||
{
|
||||
return mappingExpression.EqualityComparison((one, two) => false);
|
||||
}
|
||||
where T2 : class
|
||||
=> mappingExpression.EqualityComparison((one, two) => false);
|
||||
|
||||
public static IMappingExpression<T1, T2> EqualityById<T1, T2>(this IMappingExpression<T1, T2> mappingExpression)
|
||||
where T1 : class, IEntity
|
||||
where T2 : class, IEntity
|
||||
{
|
||||
return mappingExpression.EqualityComparison((one, two) => one.Id == two.Id);
|
||||
}
|
||||
where T2 : class, IEntity
|
||||
=> mappingExpression.EqualityComparison((one, two) => one.Id == two.Id);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
using Microsoft.AspNetCore.Hosting;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.IdentityModel.Tokens;
|
||||
using Sia.Gateway.Hubs;
|
||||
using Sia.Gateway.Middleware;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
@ -14,6 +15,9 @@ namespace Sia.Gateway.Initialization
|
|||
|
||||
public static void AddMiddleware(this IApplicationBuilder app, IHostingEnvironment env, IConfigurationRoot configuration)
|
||||
{
|
||||
app.UseAuthentication();
|
||||
app.UseSession();
|
||||
|
||||
app.UseCors(builder =>
|
||||
builder
|
||||
.WithOrigins(LoadAcceptableOriginsFromConfig(configuration))
|
||||
|
@ -21,8 +25,11 @@ namespace Sia.Gateway.Initialization
|
|||
.AllowAnyMethod()
|
||||
.AllowCredentials()
|
||||
);
|
||||
app.UseAuthentication();
|
||||
app.UseSession();
|
||||
|
||||
app.UseSignalR(routes =>
|
||||
{
|
||||
routes.MapHub<EventsHub>(EventsHub.HubPath);
|
||||
});
|
||||
|
||||
if (env.IsDevelopment() || env.IsStaging()) app.UseDeveloperExceptionPage();
|
||||
app.UseMiddleware<ExceptionHandler>();
|
||||
|
|
|
@ -1,22 +0,0 @@
|
|||
using Microsoft.AspNetCore.Hosting;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Sia.Shared.Authentication;
|
||||
|
||||
namespace Sia.Gateway.Initialization
|
||||
{
|
||||
public static class SecretVaultStartup
|
||||
{
|
||||
public static AzureSecretVault Initialize(IHostingEnvironment env, IConfigurationRoot configuration)
|
||||
{
|
||||
//Needs to be done in the initial Startup.Startup() method because Application Insights registers itself prior
|
||||
//to ConfigureServices being run
|
||||
var secrets = new AzureSecretVault(configuration);
|
||||
|
||||
var vaultTask = secrets.Get(configuration.GetSection("KeyVault")["InstrumentationKeyName"]);
|
||||
vaultTask.Wait();
|
||||
configuration.GetSection("ApplicationInsights")["InstrumentationKey"] = vaultTask.Result;
|
||||
|
||||
return secrets;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,6 +1,7 @@
|
|||
using MediatR;
|
||||
using Microsoft.AspNetCore.Authentication.JwtBearer;
|
||||
using Microsoft.AspNetCore.Hosting;
|
||||
using Microsoft.AspNetCore.SignalR.Client;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.AspNetCore.Mvc.Infrastructure;
|
||||
using Microsoft.AspNetCore.Mvc.Routing;
|
||||
|
@ -12,10 +13,14 @@ using Sia.Connectors.Tickets.TicketProxy;
|
|||
using Sia.Data.Incidents;
|
||||
using Sia.Gateway.Authentication;
|
||||
using Sia.Gateway.Requests;
|
||||
using Sia.Gateway.ServiceRepositories;
|
||||
using Sia.Shared.Authentication;
|
||||
using Sia.Shared.Validation;
|
||||
using System;
|
||||
using System.Reflection;
|
||||
using System.Runtime.Loader;
|
||||
using Sia.Gateway.Protocol;
|
||||
using System.Buffers;
|
||||
using Sia.Domain;
|
||||
|
||||
namespace Sia.Gateway.Initialization
|
||||
{
|
||||
|
@ -24,45 +29,71 @@ namespace Sia.Gateway.Initialization
|
|||
|
||||
public static void AddFirstPartyServices(this IServiceCollection services, IHostingEnvironment env, IConfigurationRoot config)
|
||||
{
|
||||
ConfigureAuth(services, config);
|
||||
|
||||
var incidentAuthConfig = new AzureActiveDirectoryAuthenticationInfo(config["Incident:ClientId"], config["Incident:ClientSecret"], config["AzureAd:Tenant"]);
|
||||
|
||||
if (env.IsDevelopment()) services.AddDbContext<IncidentContext>(options => options.UseInMemoryDatabase("Live"));
|
||||
if (env.IsStaging()) services.AddDbContext<IncidentContext>(options => options.UseSqlServer(config.GetConnectionString("incidentStaging")));
|
||||
|
||||
var ticketConnectorAssemblyPath = config["Connector:Ticket:Path"];
|
||||
|
||||
if (!string.IsNullOrEmpty(ticketConnectorAssemblyPath))
|
||||
{
|
||||
LoadConnectorFromAssembly(services, env, config, ticketConnectorAssemblyPath);
|
||||
}
|
||||
else
|
||||
{
|
||||
var proxyEndpoint = config["Connector:Ticket:ProxyEndpoint"];
|
||||
if (!string.IsNullOrEmpty(proxyEndpoint))
|
||||
{
|
||||
services.AddIncidentClient(typeof(Ticket));
|
||||
var proxyAuthType = config["Connector:Ticket:ProxyAuthType"];
|
||||
if (proxyAuthType == "Certificate")
|
||||
{
|
||||
services.AddProxyWithCert(proxyEndpoint, config["Connector:Ticket:ProxyCertThumbprint"]);
|
||||
}
|
||||
else
|
||||
{
|
||||
services.AddProxyWithoutAuth(proxyEndpoint);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
services.AddIncidentClient(typeof(EmptyTicket));
|
||||
services.AddNoTicketingSystem();
|
||||
}
|
||||
}
|
||||
|
||||
services.AddScoped<IEventRepository, EventRepository>();
|
||||
services.AddScoped<IEngagementRepository, EngagementRepository>();
|
||||
services.AddTicketingConnector(env, config);
|
||||
|
||||
services.AddSingleton<IConfigurationRoot>(i => config);
|
||||
}
|
||||
|
||||
private static void AddTicketingConnector(this IServiceCollection services, IHostingEnvironment env, IConfigurationRoot config)
|
||||
{
|
||||
if (TryGetConfigValue(config, "Connector:Ticket:Path", out var ticketConnectorAssemblyPath))
|
||||
{
|
||||
LoadConnectorFromAssembly(services, env, config, ticketConnectorAssemblyPath);
|
||||
return;
|
||||
}
|
||||
|
||||
if (TryGetConfigValue(config, "Connector:Ticket:ProxyEndpoint", out var proxyEndpoint))
|
||||
{
|
||||
AddProxyConnector(services, config, proxyEndpoint);
|
||||
return;
|
||||
}
|
||||
|
||||
services.AddIncidentClient(typeof(EmptyTicket));
|
||||
services.AddNoTicketingSystem();
|
||||
}
|
||||
|
||||
private static bool TryGetConfigValue(this IConfigurationRoot config, string configName, out string configValue)
|
||||
{
|
||||
ThrowIf.NullOrWhiteSpace(configName, nameof(configName));
|
||||
configValue = config[configName];
|
||||
return !string.IsNullOrEmpty(configValue);
|
||||
}
|
||||
|
||||
private static void AddProxyConnector(IServiceCollection services, IConfigurationRoot config, string proxyEndpoint)
|
||||
{
|
||||
services.AddIncidentClient(typeof(ProxyTicket));
|
||||
var proxyAuthType = config["Connector:Ticket:ProxyAuthType"];
|
||||
switch(proxyAuthType)
|
||||
{
|
||||
case "Certificate":
|
||||
services.AddProxyWithCert(proxyEndpoint, config["Connector:Ticket:ProxyCertThumbprint"]);
|
||||
return;
|
||||
case "VaultCertificate":
|
||||
services.AddProxyWithCertFromKeyVault(
|
||||
proxyEndpoint,
|
||||
new KeyVaultConfiguration(
|
||||
config["ClientId"],
|
||||
config["ClientSecret"],
|
||||
config["Connector:Ticket:VaultName"]
|
||||
),
|
||||
config["Connector:Ticket:CertName"]
|
||||
);
|
||||
return;
|
||||
default:
|
||||
services.AddProxyWithoutAuth(proxyEndpoint);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
private static void ConfigureAuth(IServiceCollection services, IConfigurationRoot config)
|
||||
{
|
||||
var incidentAuthConfig = new AzureActiveDirectoryAuthenticationInfo(config["Incident:ClientId"], config["Incident:ClientSecret"], config["AzureAd:Tenant"]);
|
||||
services.AddSingleton<AzureActiveDirectoryAuthenticationInfo>(i => incidentAuthConfig);
|
||||
}
|
||||
|
||||
|
@ -80,21 +111,24 @@ namespace Sia.Gateway.Initialization
|
|||
|
||||
private static void AddIncidentClient(this IServiceCollection services, Type ticketType)
|
||||
{
|
||||
var clientType = typeof(IncidentRepository<>).MakeGenericType(new Type[] { ticketType });
|
||||
services.AddScoped(typeof(IIncidentRepository), clientType);
|
||||
var handlerType = typeof(GetIncidentHandler<>).MakeGenericType(new Type[] { ticketType });
|
||||
services.AddScoped(typeof(IGetIncidentHandler), handlerType);
|
||||
services.AddScoped<IAsyncRequestHandler<GetIncidentRequest, Incident>, GetIncidentHandlerWrapper>();
|
||||
}
|
||||
|
||||
public static void AddThirdPartyServices(this IServiceCollection services, IConfigurationRoot config)
|
||||
{
|
||||
//Adds every request type in the Sia.Gateway assembly
|
||||
services.AddMediatR(typeof(GetIncidentRequest).GetTypeInfo().Assembly);
|
||||
|
||||
services.AddSingleton<IActionContextAccessor, ActionContextAccessor>();
|
||||
services.AddScoped<IUrlHelper, UrlHelper>(iFactory
|
||||
=> new UrlHelper(iFactory.GetService<IActionContextAccessor>().ActionContext)
|
||||
);
|
||||
|
||||
services.AddMvc();
|
||||
services.AddMvc(options =>
|
||||
{
|
||||
options.OutputFormatters.Insert(0, new PartialSerializedJsonOutputFormatter(
|
||||
new MvcJsonOptions().SerializerSettings,
|
||||
ArrayPool<char>.Shared));
|
||||
});
|
||||
services
|
||||
.AddAuthentication(authOptions =>
|
||||
{
|
||||
|
@ -110,6 +144,31 @@ namespace Sia.Gateway.Initialization
|
|||
services.AddDistributedMemoryCache();
|
||||
services.AddSession();
|
||||
services.AddCors();
|
||||
services.AddSockets();
|
||||
services.AddSignalR(config);
|
||||
services.AddScoped<HubConnectionBuilder>();
|
||||
|
||||
//Adds every request type in the Sia.Gateway assembly
|
||||
services.AddMediatR(typeof(GetIncidentRequest).GetTypeInfo().Assembly);
|
||||
}
|
||||
|
||||
private static IServiceCollection AddSignalR(this IServiceCollection services, IConfigurationRoot config)
|
||||
{
|
||||
var signalRBuilder = services.AddSignalR();
|
||||
if (config.TryGetConfigValue("Redis:CacheEndpoint", out string cacheEndpoint)
|
||||
&& config.TryGetConfigValue("Redis:Password", out string cachePassword))
|
||||
{
|
||||
signalRBuilder.AddRedis(redisOptions =>
|
||||
{
|
||||
redisOptions.Options.EndPoints.Add(cacheEndpoint);
|
||||
redisOptions.Options.Ssl = true;
|
||||
redisOptions.Options.Password = cachePassword;
|
||||
});
|
||||
}
|
||||
|
||||
return services;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
|
@ -25,13 +25,12 @@ namespace Sia.Gateway
|
|||
}
|
||||
_configuration = builder.Build();
|
||||
|
||||
_secrets = SecretVaultStartup.Initialize(env, _configuration);
|
||||
env.InitializeApplicationInsights(_configuration);
|
||||
_env = env;
|
||||
}
|
||||
|
||||
private IHostingEnvironment _env { get; }
|
||||
private IConfigurationRoot _configuration { get; }
|
||||
private ISecretVault _secrets { get; }
|
||||
|
||||
// This method gets called by the runtime. Use this method to add services to the container.
|
||||
public void ConfigureServices(IServiceCollection services)
|
||||
|
|
|
@ -4,20 +4,20 @@ using Newtonsoft.Json;
|
|||
|
||||
namespace Sia.Gateway.Protocol
|
||||
{
|
||||
public class PaginationHeader
|
||||
public class LinksHeader
|
||||
{
|
||||
private PaginationMetadata _metadata;
|
||||
private IUrlHelper _urlHelper;
|
||||
private string _routeName;
|
||||
|
||||
public PaginationHeader(PaginationMetadata metadata, IUrlHelper urlHelper, string routeName)
|
||||
public LinksHeader(PaginationMetadata metadata, IUrlHelper urlHelper, string routeName)
|
||||
{
|
||||
_metadata = metadata;
|
||||
_urlHelper = urlHelper;
|
||||
_routeName = routeName;
|
||||
}
|
||||
|
||||
public string HeaderName => "X-Pagination";
|
||||
public const string HeaderName = "links";
|
||||
public StringValues HeaderValues => JsonConvert.SerializeObject(new
|
||||
{
|
||||
PageNumber = _metadata.PageNumber,
|
||||
|
@ -37,10 +37,10 @@ namespace Microsoft.AspNetCore.Mvc
|
|||
|
||||
public static class PaginationExtensions
|
||||
{
|
||||
public static void AddPagination(this IHeaderDictionary headers, PaginationHeader header)
|
||||
public static void AddPagination(this IHeaderDictionary headers, LinksHeader header)
|
||||
{
|
||||
headers.Add("Access-Control-Expose-Headers", "X-Pagination");
|
||||
headers.Add(header.HeaderName, header.HeaderValues);
|
||||
headers.Add("Access-Control-Expose-Headers", LinksHeader.HeaderName);
|
||||
headers.Add(LinksHeader.HeaderName, header.HeaderValues);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,51 @@
|
|||
using Microsoft.AspNetCore.Mvc.Formatters;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Newtonsoft.Json;
|
||||
using System.Buffers;
|
||||
using System.Text;
|
||||
using Sia.Shared.Data;
|
||||
|
||||
namespace Sia.Gateway.Protocol
|
||||
{
|
||||
public class PartialSerializedJsonOutputFormatter : JsonOutputFormatter
|
||||
{
|
||||
public PartialSerializedJsonOutputFormatter(JsonSerializerSettings serializerSettings, ArrayPool<char> charPool)
|
||||
: base(serializerSettings, charPool)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public override Task WriteResponseBodyAsync(OutputFormatterWriteContext context, Encoding selectedEncoding)
|
||||
{
|
||||
var dataStream = (IEnumerable<IJsonDataObject>)context.Object;
|
||||
|
||||
foreach (var objectToWrite in dataStream)
|
||||
{
|
||||
var jsonData = objectToWrite.Data;
|
||||
if (jsonData is string) objectToWrite.Data = Deserialize((string)jsonData);
|
||||
}
|
||||
|
||||
return base.WriteResponseBodyAsync(context, selectedEncoding);
|
||||
}
|
||||
|
||||
private const int NumberOfCharactersInGenericTypeNotUsedByGetInterfaceMethod = 3;
|
||||
|
||||
protected override bool CanWriteType(Type type)
|
||||
{
|
||||
if (!type.IsGenericType) return false;
|
||||
if (type.GetGenericArguments().Count() != 1) return false;
|
||||
|
||||
var enumIntName = typeof(IEnumerable<>).ToString();
|
||||
var enumerableInterface = type.GetInterface(enumIntName
|
||||
.Substring(0, enumIntName.Length - NumberOfCharactersInGenericTypeNotUsedByGetInterfaceMethod));
|
||||
if (enumerableInterface is null) return false;
|
||||
|
||||
return !(type.GetGenericArguments()[0].GetInterface(nameof(IJsonDataObject)) is null);
|
||||
}
|
||||
|
||||
private object Deserialize(string serializedData) => JsonConvert.DeserializeObject(serializedData);
|
||||
}
|
||||
}
|
|
@ -1,9 +1,13 @@
|
|||
using MediatR;
|
||||
using AutoMapper;
|
||||
using MediatR;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Sia.Data.Incidents;
|
||||
using Sia.Domain;
|
||||
using Sia.Gateway.Authentication;
|
||||
using Sia.Gateway.ServiceRepositories;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
|
||||
namespace Sia.Gateway.Requests
|
||||
{
|
||||
public class GetEngagementRequest : AuthenticatedRequest, IRequest<Engagement>
|
||||
|
@ -17,17 +21,24 @@ namespace Sia.Gateway.Requests
|
|||
public long Id { get; }
|
||||
public long IncidentId { get; }
|
||||
}
|
||||
public class GetEngagementHandler : IAsyncRequestHandler<GetEngagementRequest, Engagement>
|
||||
{
|
||||
private IEngagementRepository _incidentRepository;
|
||||
|
||||
public GetEngagementHandler(IEngagementRepository incidentRepository)
|
||||
public class GetEngagementHandler
|
||||
: IAsyncRequestHandler<GetEngagementRequest, Engagement>
|
||||
{
|
||||
private readonly IncidentContext _context;
|
||||
|
||||
public GetEngagementHandler(IncidentContext context)
|
||||
{
|
||||
_incidentRepository = incidentRepository;
|
||||
_context = context;
|
||||
}
|
||||
public async Task<Engagement> Handle(GetEngagementRequest request)
|
||||
{
|
||||
return await _incidentRepository.GetEngagementAsync(request.IncidentId, request.Id, request.UserContext);
|
||||
var EngagementRecord = await _context.Engagements
|
||||
.Include(en => en.Participant)
|
||||
.FirstOrDefaultAsync(ev => ev.IncidentId == request.IncidentId && ev.Id == request.Id);
|
||||
if (EngagementRecord == null) throw new KeyNotFoundException();
|
||||
|
||||
return Mapper.Map<Engagement>(EngagementRecord);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,9 +1,14 @@
|
|||
using MediatR;
|
||||
using AutoMapper;
|
||||
using MediatR;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Sia.Data.Incidents;
|
||||
using Sia.Domain;
|
||||
using Sia.Domain.ApiModels;
|
||||
using Sia.Gateway.Authentication;
|
||||
using Sia.Gateway.ServiceRepositories;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Sia.Gateway.Requests
|
||||
{
|
||||
public class PostEngagementRequest : AuthenticatedRequest, IRequest<Engagement>
|
||||
|
@ -18,18 +23,33 @@ namespace Sia.Gateway.Requests
|
|||
public NewEngagement NewEngagement { get; }
|
||||
public long IncidentId { get; }
|
||||
}
|
||||
public class PostEngagementHandler : IAsyncRequestHandler<PostEngagementRequest, Engagement>
|
||||
|
||||
public class PostEngagementHandler
|
||||
: IAsyncRequestHandler<PostEngagementRequest, Engagement>
|
||||
{
|
||||
private IEngagementRepository _engagementRepository;
|
||||
private readonly IncidentContext _context;
|
||||
|
||||
public PostEngagementHandler(IEngagementRepository engagementRepository)
|
||||
public PostEngagementHandler(IncidentContext context)
|
||||
{
|
||||
_engagementRepository = engagementRepository;
|
||||
_context = context;
|
||||
}
|
||||
|
||||
public async Task<Engagement> Handle(PostEngagementRequest request)
|
||||
{
|
||||
return await _engagementRepository.PostEngagementAsync(request.IncidentId, request.NewEngagement, request.UserContext);
|
||||
if (request.NewEngagement == null) throw new ArgumentNullException(nameof(request.NewEngagement));
|
||||
|
||||
var dataIncident = await _context.Incidents
|
||||
.Include(cr => cr.Engagements)
|
||||
.ThenInclude(en => en.Participant)
|
||||
.FirstOrDefaultAsync(x => x.Id == request.IncidentId);
|
||||
if (dataIncident == null) throw new KeyNotFoundException();
|
||||
|
||||
var dataEngagement = Mapper.Map<Data.Incidents.Models.Engagement>(request.NewEngagement);
|
||||
dataEngagement.TimeEngaged = DateTime.UtcNow;
|
||||
|
||||
dataIncident.Engagements.Add(dataEngagement);
|
||||
await _context.SaveChangesAsync();
|
||||
|
||||
return Mapper.Map<Engagement>(dataEngagement);
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
|
|
@ -1,9 +1,13 @@
|
|||
using MediatR;
|
||||
using AutoMapper;
|
||||
using MediatR;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Sia.Data.Incidents;
|
||||
using Sia.Domain.ApiModels;
|
||||
using Sia.Gateway.Authentication;
|
||||
using Sia.Gateway.ServiceRepositories;
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
|
||||
namespace Sia.Gateway.Requests
|
||||
{
|
||||
public class PutEngagementRequest : AuthenticatedRequest, IRequest
|
||||
|
@ -19,18 +23,26 @@ namespace Sia.Gateway.Requests
|
|||
public long EngagementId { get; }
|
||||
public long IncidentId { get; }
|
||||
}
|
||||
public class PutEngagementHandler : IAsyncRequestHandler<PutEngagementRequest>
|
||||
|
||||
public class PutEngagementHandler
|
||||
: IAsyncRequestHandler<PutEngagementRequest>
|
||||
{
|
||||
private IEngagementRepository _engagementClient;
|
||||
private readonly IncidentContext _context;
|
||||
|
||||
public PutEngagementHandler(IEngagementRepository incidentClient)
|
||||
public PutEngagementHandler(IncidentContext context)
|
||||
{
|
||||
_engagementClient = incidentClient;
|
||||
_context = context;
|
||||
}
|
||||
|
||||
public async Task Handle(PutEngagementRequest request)
|
||||
{
|
||||
await _engagementClient.PutEngagementAsync(request.IncidentId, request.EngagementId, request.UpdateEngagement, request.UserContext);
|
||||
if (request.UpdateEngagement == null) throw new ArgumentNullException(nameof(UpdateEngagement));
|
||||
var existingRecord = await _context.Engagements
|
||||
.Include(en => en.Participant)
|
||||
.FirstOrDefaultAsync(engagement => engagement.IncidentId == request.IncidentId && engagement.Id == request.EngagementId);
|
||||
|
||||
var updatedModel = Mapper.Map(request.UpdateEngagement, existingRecord);
|
||||
|
||||
await _context.SaveChangesAsync();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,21 +0,0 @@
|
|||
using MediatR;
|
||||
using Sia.Gateway.ServiceRepositories;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Sia.Gateway.Requests.Events
|
||||
{
|
||||
public abstract class EventHandler<TRequest, TReturn> : IAsyncRequestHandler<TRequest, TReturn>
|
||||
where TRequest : IRequest<TReturn>
|
||||
{
|
||||
protected EventHandler(IEventRepository eventRepository)
|
||||
{
|
||||
_eventRepository = eventRepository;
|
||||
}
|
||||
protected IEventRepository _eventRepository;
|
||||
|
||||
public abstract Task<TReturn> Handle(TRequest request);
|
||||
}
|
||||
}
|
|
@ -1,8 +1,10 @@
|
|||
using MediatR;
|
||||
using AutoMapper;
|
||||
using MediatR;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Sia.Data.Incidents;
|
||||
using Sia.Domain;
|
||||
using Sia.Gateway.Authentication;
|
||||
using Sia.Gateway.Requests.Events;
|
||||
using Sia.Gateway.ServiceRepositories;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Sia.Gateway.Requests
|
||||
|
@ -19,17 +21,25 @@ namespace Sia.Gateway.Requests
|
|||
public long Id { get; }
|
||||
public long IncidentId { get; }
|
||||
}
|
||||
public class GetEventHandler : EventHandler<GetEventRequest, Event>
|
||||
{
|
||||
protected GetEventHandler(IEventRepository eventRepository)
|
||||
: base(eventRepository)
|
||||
{
|
||||
}
|
||||
|
||||
public override async Task<Event> Handle(GetEventRequest request)
|
||||
public class GetEventHandler : IAsyncRequestHandler<GetEventRequest, Event>
|
||||
{
|
||||
private readonly IncidentContext _context;
|
||||
|
||||
public GetEventHandler(IncidentContext context)
|
||||
{
|
||||
return await _eventRepository.GetEvent(request.IncidentId, request.Id, request.UserContext);
|
||||
_context = context;
|
||||
}
|
||||
public async Task<Event> Handle(GetEventRequest request)
|
||||
{
|
||||
var eventRecord = await _context
|
||||
.Events
|
||||
.FirstOrDefaultAsync( ev
|
||||
=> ev.IncidentId == request.IncidentId
|
||||
&& ev.Id == request.Id);
|
||||
if (eventRecord == null) throw new KeyNotFoundException();
|
||||
|
||||
return Mapper.Map<Event>(eventRecord);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -1,12 +1,13 @@
|
|||
using MediatR;
|
||||
using Sia.Domain;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Sia.Gateway.Authentication;
|
||||
using Sia.Gateway.Protocol;
|
||||
using Sia.Gateway.ServiceRepositories;
|
||||
using Sia.Data.Incidents;
|
||||
using AutoMapper.QueryableExtensions;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
namespace Sia.Gateway.Requests.Events
|
||||
{
|
||||
|
@ -23,16 +24,20 @@ namespace Sia.Gateway.Requests.Events
|
|||
public PaginationMetadata Pagination { get; }
|
||||
}
|
||||
|
||||
public class GetEventsHandler : EventHandler<GetEventsRequest, IEnumerable<Event>>
|
||||
public class GetEventsHandler
|
||||
: IAsyncRequestHandler<GetEventsRequest, IEnumerable<Event>>
|
||||
{
|
||||
public GetEventsHandler(IEventRepository eventRepository)
|
||||
: base(eventRepository)
|
||||
{
|
||||
}
|
||||
private readonly IncidentContext _context;
|
||||
|
||||
public override async Task<IEnumerable<Event>> Handle(GetEventsRequest request)
|
||||
public GetEventsHandler(IncidentContext context)
|
||||
{
|
||||
return await _eventRepository.GetEventsAsync(request.IncidentId, request.Pagination, request.UserContext);
|
||||
_context = context;
|
||||
}
|
||||
public async Task<IEnumerable<Event>> Handle(GetEventsRequest request)
|
||||
=> await _context.Events
|
||||
.Where(ev => ev.IncidentId == request.IncidentId)
|
||||
.WithPagination(request.Pagination)
|
||||
.ProjectTo<Event>()
|
||||
.ToListAsync();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,8 +1,12 @@
|
|||
using MediatR;
|
||||
using AutoMapper;
|
||||
using MediatR;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Sia.Data.Incidents;
|
||||
using Sia.Domain;
|
||||
using Sia.Domain.ApiModels;
|
||||
using Sia.Gateway.Authentication;
|
||||
using Sia.Gateway.ServiceRepositories;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Sia.Gateway.Requests
|
||||
|
@ -19,18 +23,31 @@ namespace Sia.Gateway.Requests
|
|||
public NewEvent NewEvent { get; }
|
||||
public long IncidentId { get; }
|
||||
}
|
||||
|
||||
public class PostEventHandler : IAsyncRequestHandler<PostEventRequest, Event>
|
||||
{
|
||||
private IEventRepository _incidentRepository;
|
||||
private readonly IncidentContext _context;
|
||||
|
||||
public PostEventHandler(IEventRepository incidentRepository)
|
||||
public PostEventHandler(IncidentContext context)
|
||||
{
|
||||
_incidentRepository = incidentRepository;
|
||||
_context = context;
|
||||
}
|
||||
|
||||
public async Task<Event> Handle(PostEventRequest request)
|
||||
{
|
||||
return await _incidentRepository.PostEvent(request.IncidentId, request.NewEvent, request.UserContext);
|
||||
if (request.NewEvent == null) throw new ArgumentNullException(nameof(request.NewEvent));
|
||||
|
||||
var dataCrisis = await _context
|
||||
.Incidents
|
||||
.Include(cr => cr.Events)
|
||||
.FirstOrDefaultAsync(x => x.Id == request.IncidentId);
|
||||
if (dataCrisis == null) throw new KeyNotFoundException();
|
||||
|
||||
var dataEvent = Mapper.Map<Data.Incidents.Models.Event>(request.NewEvent);
|
||||
|
||||
dataCrisis.Events.Add(dataEvent);
|
||||
await _context.SaveChangesAsync();
|
||||
|
||||
return Mapper.Map<Event>(dataEvent);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,7 +1,11 @@
|
|||
using MediatR;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Sia.Connectors.Tickets;
|
||||
using Sia.Data.Incidents;
|
||||
using Sia.Domain;
|
||||
using Sia.Gateway.Authentication;
|
||||
using Sia.Gateway.ServiceRepositories;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Sia.Gateway.Requests
|
||||
|
@ -15,18 +19,53 @@ namespace Sia.Gateway.Requests
|
|||
}
|
||||
public long Id { get; }
|
||||
}
|
||||
public class GetIncidentHandler : IAsyncRequestHandler<GetIncidentRequest, Incident>
|
||||
|
||||
public class GetIncidentHandler<TTicket> : IGetIncidentHandler
|
||||
{
|
||||
private IIncidentRepository _incidentRepository;
|
||||
|
||||
public GetIncidentHandler(IIncidentRepository incidentRepository)
|
||||
private readonly IncidentContext _context;
|
||||
private readonly Connector<TTicket> _connector;
|
||||
public GetIncidentHandler(IncidentContext context, Connector<TTicket> connector)
|
||||
{
|
||||
_incidentRepository = incidentRepository;
|
||||
_context = context;
|
||||
_connector = connector;
|
||||
}
|
||||
|
||||
public async Task<Incident> Handle(GetIncidentRequest request)
|
||||
public async Task<Incident> Handle(GetIncidentRequest getIncident)
|
||||
{
|
||||
return await _incidentRepository.GetIncidentAsync(request.Id, request.UserContext);
|
||||
var incidentRecord = await _context.Incidents
|
||||
.WithEagerLoading()
|
||||
.FirstOrDefaultAsync(cr => cr.Id == getIncident.Id);
|
||||
if (incidentRecord == null) throw new KeyNotFoundException();
|
||||
|
||||
var remoteId = incidentRecord
|
||||
.Tickets
|
||||
.FirstOrDefault(t => t.IsPrimary)
|
||||
.OriginId;
|
||||
|
||||
var ticket = await _connector.Client.GetAsync(remoteId);
|
||||
|
||||
return _connector
|
||||
.Converter
|
||||
.AssembleIncident(incidentRecord, ticket);
|
||||
}
|
||||
}
|
||||
|
||||
public interface IGetIncidentHandler
|
||||
{
|
||||
Task<Incident> Handle(GetIncidentRequest getIncident);
|
||||
}
|
||||
|
||||
//Why does this exist?
|
||||
//Purely because I haven't been able to get Mediatr to work with generics
|
||||
public class GetIncidentHandlerWrapper : IAsyncRequestHandler<GetIncidentRequest, Incident>
|
||||
{
|
||||
private readonly IGetIncidentHandler _actualHandler;
|
||||
|
||||
public GetIncidentHandlerWrapper(IGetIncidentHandler actualHandler)
|
||||
{
|
||||
_actualHandler = actualHandler;
|
||||
}
|
||||
|
||||
public Task<Incident> Handle(GetIncidentRequest message)
|
||||
=> _actualHandler.Handle(message);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,7 +1,9 @@
|
|||
using MediatR;
|
||||
using AutoMapper.QueryableExtensions;
|
||||
using MediatR;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Sia.Data.Incidents;
|
||||
using Sia.Domain;
|
||||
using Sia.Gateway.Authentication;
|
||||
using Sia.Gateway.ServiceRepositories;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
|
@ -9,23 +11,28 @@ namespace Sia.Gateway.Requests
|
|||
{
|
||||
public class GetIncidentsRequest : AuthenticatedRequest, IRequest<IEnumerable<Incident>>
|
||||
{
|
||||
public GetIncidentsRequest(AuthenticatedUserContext userContext) : base(userContext)
|
||||
public GetIncidentsRequest(AuthenticatedUserContext userContext)
|
||||
: base(userContext)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
public class GetIncidentsHandler : IAsyncRequestHandler<GetIncidentsRequest, IEnumerable<Incident>>
|
||||
public class GetIncidentsHandler
|
||||
: IAsyncRequestHandler<GetIncidentsRequest, IEnumerable<Incident>>
|
||||
{
|
||||
private IIncidentRepository _incidentRepository;
|
||||
|
||||
public GetIncidentsHandler(IIncidentRepository incidentClient)
|
||||
private readonly IncidentContext _context;
|
||||
public GetIncidentsHandler(IncidentContext context)
|
||||
{
|
||||
_incidentRepository = incidentClient;
|
||||
_context = context;
|
||||
}
|
||||
public async Task<IEnumerable<Incident>> Handle(GetIncidentsRequest message)
|
||||
public async Task<IEnumerable<Incident>> Handle(GetIncidentsRequest request)
|
||||
{
|
||||
var incidentResponse = await _incidentRepository.GetIncidentsAsync(message.UserContext);
|
||||
return incidentResponse;
|
||||
var incidentRecords = await _context.Incidents
|
||||
.WithEagerLoading()
|
||||
.ProjectTo<Incident>()
|
||||
.ToListAsync();
|
||||
return incidentRecords;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -1,8 +1,11 @@
|
|||
using MediatR;
|
||||
using AutoMapper.QueryableExtensions;
|
||||
using MediatR;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Sia.Data.Incidents;
|
||||
using Sia.Domain;
|
||||
using Sia.Gateway.Authentication;
|
||||
using Sia.Gateway.ServiceRepositories;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Sia.Gateway.Requests
|
||||
|
@ -17,18 +20,22 @@ namespace Sia.Gateway.Requests
|
|||
}
|
||||
}
|
||||
|
||||
public class GetIncidentsByTicketHandler : IAsyncRequestHandler<GetIncidentsByTicketRequest, IEnumerable<Incident>>
|
||||
public class GetIncidentsByTicketHandler
|
||||
: IAsyncRequestHandler<GetIncidentsByTicketRequest, IEnumerable<Incident>>
|
||||
{
|
||||
private IIncidentRepository _incidentRepository;
|
||||
|
||||
public GetIncidentsByTicketHandler(IIncidentRepository incidentClient)
|
||||
private readonly IncidentContext _context;
|
||||
public GetIncidentsByTicketHandler(IncidentContext context)
|
||||
{
|
||||
_incidentRepository = incidentClient;
|
||||
_context = context;
|
||||
}
|
||||
public async Task<IEnumerable<Incident>> Handle(GetIncidentsByTicketRequest message)
|
||||
public async Task<IEnumerable<Incident>> Handle(GetIncidentsByTicketRequest request)
|
||||
{
|
||||
var incidentResponse = await _incidentRepository.GetIncidentsByTicketAsync(message.TicketId, message.UserContext);
|
||||
return incidentResponse;
|
||||
var incidentRecords = await _context.Incidents
|
||||
.WithEagerLoading()
|
||||
.Where(incident => incident.Tickets.Any(inc => inc.OriginId == request.TicketId))
|
||||
.ProjectTo<Incident>().ToListAsync();
|
||||
|
||||
return incidentRecords;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,68 @@
|
|||
using AutoMapper;
|
||||
using AutoMapper.QueryableExtensions;
|
||||
using MediatR;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Sia.Data.Incidents;
|
||||
using Sia.Domain;
|
||||
using Sia.Domain.ApiModels;
|
||||
using Sia.Gateway.Authentication;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Sia.Gateway.Requests
|
||||
{
|
||||
public class GetIncidentsByTicketCreateIfNeededRequest : AuthenticatedRequest, IRequest<IEnumerable<Incident>>
|
||||
{
|
||||
public GetIncidentsByTicketCreateIfNeededRequest(string ticketId, AuthenticatedUserContext userContext)
|
||||
: base(userContext)
|
||||
{
|
||||
TicketId = ticketId;
|
||||
}
|
||||
|
||||
public string TicketId { get; private set; }
|
||||
|
||||
}
|
||||
|
||||
public class GetIncidentsByTicketCreateIfNeededRequestHandler : IAsyncRequestHandler<GetIncidentsByTicketCreateIfNeededRequest, IEnumerable<Incident>>
|
||||
{
|
||||
|
||||
private IncidentContext _context;
|
||||
|
||||
public GetIncidentsByTicketCreateIfNeededRequestHandler(IncidentContext context)
|
||||
{
|
||||
_context = context;
|
||||
}
|
||||
|
||||
public IncidentContext Context { get; }
|
||||
|
||||
public async Task<IEnumerable<Incident>> Handle(GetIncidentsByTicketCreateIfNeededRequest message)
|
||||
{
|
||||
var incidents = await _context.Incidents
|
||||
.WithEagerLoading()
|
||||
.Where(incident => incident.Tickets.Any(inc => inc.OriginId == message.TicketId))
|
||||
.ProjectTo<Incident>().ToListAsync();
|
||||
|
||||
if (incidents.Any())
|
||||
{
|
||||
return incidents;
|
||||
}
|
||||
|
||||
var newIncident = new NewIncident
|
||||
{
|
||||
PrimaryTicket = new Ticket
|
||||
{
|
||||
TicketingSystemId = 1,
|
||||
OriginId = message.TicketId
|
||||
}
|
||||
};
|
||||
|
||||
var dataIncident = Mapper.Map<Data.Incidents.Models.Incident>(newIncident);
|
||||
|
||||
var result = _context.Incidents.Add(dataIncident);
|
||||
await _context.SaveChangesAsync();
|
||||
|
||||
return new List<Incident> { Mapper.Map<Incident>(result.Entity) };
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,8 +1,11 @@
|
|||
using MediatR;
|
||||
using AutoMapper;
|
||||
using MediatR;
|
||||
using Sia.Data.Incidents;
|
||||
using Sia.Domain;
|
||||
using Sia.Domain.ApiModels;
|
||||
using Sia.Gateway.Authentication;
|
||||
using Sia.Gateway.ServiceRepositories;
|
||||
using Sia.Shared.Exceptions;
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Sia.Gateway.Requests
|
||||
|
@ -16,20 +19,27 @@ namespace Sia.Gateway.Requests
|
|||
}
|
||||
|
||||
public NewIncident Incident { get; private set; }
|
||||
|
||||
}
|
||||
|
||||
public class PostIncidentHandler : IAsyncRequestHandler<PostIncidentRequest, Incident>
|
||||
public class PostIncidentHandler
|
||||
: IAsyncRequestHandler<PostIncidentRequest, Incident>
|
||||
{
|
||||
private IIncidentRepository _incidentRepository;
|
||||
|
||||
public PostIncidentHandler(IIncidentRepository incidentClient)
|
||||
private readonly IncidentContext _context;
|
||||
public PostIncidentHandler(IncidentContext context)
|
||||
{
|
||||
_incidentRepository = incidentClient;
|
||||
_context = context;
|
||||
}
|
||||
public async Task<Incident> Handle(PostIncidentRequest message)
|
||||
public async Task<Incident> Handle(PostIncidentRequest request)
|
||||
{
|
||||
return await _incidentRepository.PostIncidentAsync(message.Incident, message.UserContext);
|
||||
if (request.Incident == null) throw new ArgumentNullException(nameof(request.Incident));
|
||||
if (request.Incident?.PrimaryTicket?.OriginId == null) throw new ConflictException("Please provide a primary incident with a valid originId");
|
||||
|
||||
var dataIncident = Mapper.Map<Data.Incidents.Models.Incident>(request.Incident);
|
||||
|
||||
var result = _context.Incidents.Add(dataIncident);
|
||||
await _context.SaveChangesAsync();
|
||||
|
||||
return Mapper.Map<Incident>(dataIncident);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,70 +0,0 @@
|
|||
using AutoMapper;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Sia.Data.Incidents;
|
||||
using Sia.Domain;
|
||||
using Sia.Domain.ApiModels;
|
||||
using Sia.Gateway.Authentication;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Sia.Gateway.ServiceRepositories
|
||||
{
|
||||
public interface IEngagementRepository
|
||||
{
|
||||
Task<Engagement> GetEngagementAsync(long incidentId, long id, AuthenticatedUserContext userContext);
|
||||
Task<Engagement> PostEngagementAsync(long incidentId, NewEngagement newEngagement, AuthenticatedUserContext userContext);
|
||||
Task PutEngagementAsync(long incidentId, long engagementId, UpdateEngagement updatedEngagement, AuthenticatedUserContext userContext);
|
||||
}
|
||||
|
||||
public class EngagementRepository : IEngagementRepository
|
||||
{
|
||||
private readonly IncidentContext _context;
|
||||
|
||||
public EngagementRepository(IncidentContext context)
|
||||
{
|
||||
_context = context;
|
||||
}
|
||||
|
||||
public async Task<Engagement> GetEngagementAsync(long incidentId, long id, AuthenticatedUserContext userContext)
|
||||
{
|
||||
var EngagementRecord = await _context.Engagements
|
||||
.Include(en => en.Participant)
|
||||
.FirstOrDefaultAsync(ev => ev.IncidentId == incidentId && ev.Id == id);
|
||||
if (EngagementRecord == null) throw new KeyNotFoundException();
|
||||
|
||||
return Mapper.Map<Engagement>(EngagementRecord);
|
||||
}
|
||||
|
||||
public async Task<Engagement> PostEngagementAsync(long incidentId, NewEngagement newEngagement, AuthenticatedUserContext userContext)
|
||||
{
|
||||
if (newEngagement == null) throw new ArgumentNullException(nameof(newEngagement));
|
||||
|
||||
var dataIncident = await _context.Incidents
|
||||
.Include(cr => cr.Engagements)
|
||||
.ThenInclude(en => en.Participant)
|
||||
.FirstOrDefaultAsync(x => x.Id == incidentId);
|
||||
if (dataIncident == null) throw new KeyNotFoundException();
|
||||
|
||||
var dataEngagement = Mapper.Map<Data.Incidents.Models.Engagement>(newEngagement);
|
||||
dataEngagement.TimeEngaged = DateTime.UtcNow;
|
||||
|
||||
dataIncident.Engagements.Add(dataEngagement);
|
||||
await _context.SaveChangesAsync();
|
||||
|
||||
return Mapper.Map<Engagement>(dataEngagement);
|
||||
}
|
||||
|
||||
public async Task PutEngagementAsync(long incidentId, long engagementId, UpdateEngagement updatedEngagement, AuthenticatedUserContext userContext)
|
||||
{
|
||||
if (updatedEngagement == null) throw new ArgumentNullException(nameof(updatedEngagement));
|
||||
var existingRecord = await _context.Engagements
|
||||
.Include(en => en.Participant)
|
||||
.FirstOrDefaultAsync(engagement => engagement.IncidentId == incidentId && engagement.Id == engagementId);
|
||||
|
||||
var updatedModel = Mapper.Map(updatedEngagement, existingRecord);
|
||||
|
||||
await _context.SaveChangesAsync();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,66 +0,0 @@
|
|||
using AutoMapper;
|
||||
using AutoMapper.QueryableExtensions;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Sia.Data.Incidents;
|
||||
using Sia.Domain;
|
||||
using Sia.Domain.ApiModels;
|
||||
using Sia.Gateway.Authentication;
|
||||
using Sia.Gateway.Protocol;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Sia.Gateway.ServiceRepositories
|
||||
{
|
||||
public interface IEventRepository
|
||||
{
|
||||
Task<Event> GetEvent(long incidentId, long id, AuthenticatedUserContext userContext);
|
||||
Task<Event> PostEvent(long incidentId, NewEvent newEvent, AuthenticatedUserContext userContext);
|
||||
Task<IEnumerable<Event>> GetEventsAsync(long incidentId, PaginationMetadata pagination, AuthenticatedUserContext userContext);
|
||||
}
|
||||
|
||||
public class EventRepository : IEventRepository
|
||||
{
|
||||
|
||||
private readonly IncidentContext _context;
|
||||
|
||||
public EventRepository(IncidentContext context)
|
||||
{
|
||||
_context = context;
|
||||
}
|
||||
|
||||
public async Task<Event> GetEvent(long incidentId, long id, AuthenticatedUserContext userContext)
|
||||
{
|
||||
var eventRecord = await _context.Events.FirstOrDefaultAsync(ev => ev.IncidentId == incidentId && ev.Id == id);
|
||||
if (eventRecord == null) throw new KeyNotFoundException();
|
||||
|
||||
return Mapper.Map<Event>(eventRecord);
|
||||
}
|
||||
|
||||
public async Task<IEnumerable<Event>> GetEventsAsync(long incidentId, PaginationMetadata pagination, AuthenticatedUserContext userContext)
|
||||
{
|
||||
return await _context.Events
|
||||
.WithPagination(pagination)
|
||||
.ProjectTo<Event>()
|
||||
.ToListAsync();
|
||||
}
|
||||
|
||||
public async Task<Event> PostEvent(long incidentId, NewEvent newEvent, AuthenticatedUserContext userContext)
|
||||
{
|
||||
if (newEvent == null) throw new ArgumentNullException(nameof(newEvent));
|
||||
|
||||
var dataCrisis = await _context.Incidents
|
||||
.Include(cr => cr.Events)
|
||||
.FirstOrDefaultAsync(x => x.Id == incidentId);
|
||||
if (dataCrisis == null) throw new KeyNotFoundException();
|
||||
|
||||
var dataEvent = Mapper.Map<Data.Incidents.Models.Event>(newEvent);
|
||||
|
||||
dataCrisis.Events.Add(dataEvent);
|
||||
await _context.SaveChangesAsync();
|
||||
|
||||
return Mapper.Map<Event>(dataEvent);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,75 +0,0 @@
|
|||
using AutoMapper;
|
||||
using AutoMapper.QueryableExtensions;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Sia.Connectors.Tickets;
|
||||
using Sia.Data.Incidents;
|
||||
using Sia.Domain;
|
||||
using Sia.Domain.ApiModels;
|
||||
using Sia.Gateway.Authentication;
|
||||
using Sia.Shared.Exceptions;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Sia.Gateway.ServiceRepositories
|
||||
{
|
||||
public interface IIncidentRepository
|
||||
{
|
||||
Task<Incident> GetIncidentAsync(long id, AuthenticatedUserContext userContext);
|
||||
Task<IEnumerable<Incident>> GetIncidentsAsync(AuthenticatedUserContext userContext);
|
||||
Task<Incident> PostIncidentAsync(NewIncident incident, AuthenticatedUserContext userContext);
|
||||
Task<IEnumerable<Incident>> GetIncidentsByTicketAsync(string ticketId, AuthenticatedUserContext userContext);
|
||||
}
|
||||
public class IncidentRepository<TTicket> : IIncidentRepository
|
||||
{
|
||||
private readonly IncidentContext _context;
|
||||
private readonly Connector<TTicket> _connector;
|
||||
|
||||
public IncidentRepository(IncidentContext context, Connector<TTicket> connector)
|
||||
{
|
||||
_context = context;
|
||||
_connector = connector;
|
||||
}
|
||||
|
||||
public async Task<Incident> GetIncidentAsync(long id, AuthenticatedUserContext userContext)
|
||||
{
|
||||
var incidentRecord = await _context.Incidents.WithEagerLoading().FirstOrDefaultAsync(cr => cr.Id == id);
|
||||
if (incidentRecord == null) throw new KeyNotFoundException();
|
||||
|
||||
var ticket = await _connector.Client.GetAsync(incidentRecord.Tickets.FirstOrDefault(t => t.IsPrimary).OriginId);
|
||||
|
||||
return _connector.Converter.AssembleIncident(incidentRecord, ticket);
|
||||
}
|
||||
|
||||
public async Task<IEnumerable<Incident>> GetIncidentsAsync(AuthenticatedUserContext userContext)
|
||||
{
|
||||
var incidentRecords = await _context.Incidents.WithEagerLoading().ProjectTo<Incident>().ToListAsync();
|
||||
return incidentRecords;
|
||||
}
|
||||
|
||||
public async Task<IEnumerable<Incident>> GetIncidentsByTicketAsync(string ticketId, AuthenticatedUserContext userContext)
|
||||
{
|
||||
var incidentRecords = await _context.Incidents
|
||||
.WithEagerLoading()
|
||||
.Where(incident => incident.Tickets.Any(inc => inc.OriginId == ticketId))
|
||||
.ProjectTo<Incident>().ToListAsync();
|
||||
|
||||
return incidentRecords;
|
||||
}
|
||||
|
||||
public async Task<Incident> PostIncidentAsync(NewIncident incident, AuthenticatedUserContext userContext)
|
||||
{
|
||||
if (incident == null) throw new ArgumentNullException(nameof(incident));
|
||||
if (incident?.PrimaryTicket?.OriginId == null) throw new ConflictException("Please provide a primary incident with a valid originId");
|
||||
|
||||
var dataIncident = Mapper.Map<Data.Incidents.Models.Incident>(incident);
|
||||
|
||||
var result = _context.Incidents.Add(dataIncident);
|
||||
await _context.SaveChangesAsync();
|
||||
|
||||
return Mapper.Map<Incident>(dataIncident);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
|
@ -14,6 +14,9 @@
|
|||
<PackageReference Include="Microsoft.AspNetCore.Mvc" Version="2.0.0" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Mvc.Cors" Version="2.0.0" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Session" Version="2.0.0" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.SignalR" Version="1.0.0-alpha1-final" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.SignalR.Client" Version="1.0.0-alpha1-final" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.SignalR.Redis" Version="1.0.0-alpha1-final" />
|
||||
<PackageReference Include="Microsoft.Azure.KeyVault" Version="2.3.2" />
|
||||
<PackageReference Include="Microsoft.Azure.KeyVault.Core" Version="2.0.4" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" Version="2.0.0" />
|
||||
|
@ -21,13 +24,13 @@
|
|||
<PackageReference Include="Microsoft.Extensions.Configuration.UserSecrets" Version="2.0.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.Logging.Debug" Version="2.0.0" />
|
||||
<PackageReference Include="Microsoft.IdentityModel.Clients.ActiveDirectory" Version="3.16.1" />
|
||||
<PackageReference Include="Microsoft.Sia.Connectors.Tickets" Version="1.0.5-alpha" />
|
||||
<PackageReference Include="System.Globalization.Extensions" Version="4.3.0" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<DotNetCliToolReference Include="Microsoft.VisualStudio.Web.CodeGeneration.Tools" Version="1.0.0" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\Sia.Connectors.Tickets\Sia.Connectors.Tickets.csproj" />
|
||||
<ProjectReference Include="..\Sia.Data.Incident\Sia.Data.Incidents.csproj" />
|
||||
<ProjectReference Include="..\Sia.Domain.ApiModels\Sia.Domain.ApiModels.csproj" />
|
||||
<ProjectReference Include="..\Sia.Domain\Sia.Domain.csproj" />
|
||||
|
|
|
@ -15,5 +15,26 @@
|
|||
"Your uri here" //Replace with your frontend uri
|
||||
]
|
||||
},
|
||||
"Redis": {
|
||||
"CacheEndpoint": "yourCache.redis.cache.windows.net:6380",
|
||||
"Password": "YOURPASSWORD"
|
||||
},
|
||||
"Connector": {
|
||||
"Ticket": {
|
||||
//If loading from assembly
|
||||
"Path": "Path to connector DLL",
|
||||
|
||||
//If using proxy
|
||||
"ProxyEndpoint": "endpoint URL",
|
||||
"ProxyAuthType": "Proxy authentication type (Certificate, VaultCertificate, or None)",
|
||||
|
||||
//If using 'Certificate' proxy auth type
|
||||
"ProxyCertThumbprint": "Certificate thumbprint",
|
||||
|
||||
//If using VaultCertificate auth with either assembly or proxy
|
||||
"CertName": "certificate name as stored in KeyVault",
|
||||
"VaultName": "name of KeyVault instance to use when retrieving certificate"
|
||||
}
|
||||
},
|
||||
"ClientSecret": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaa" //Replace with your client secret
|
||||
}
|
||||
|
|
|
@ -2,7 +2,9 @@
|
|||
using Microsoft.Azure.KeyVault.Models;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.IdentityModel.Clients.ActiveDirectory;
|
||||
using Sia.Shared.Validation;
|
||||
using System;
|
||||
using System.Security.Cryptography.X509Certificates;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Sia.Shared.Authentication
|
||||
|
@ -13,35 +15,48 @@ namespace Sia.Shared.Authentication
|
|||
}
|
||||
public class AzureSecretVault : ISecretVault
|
||||
{
|
||||
private readonly string _vault;
|
||||
private readonly string _clientId;
|
||||
private readonly string _secret;
|
||||
private readonly KeyVaultConfiguration _config;
|
||||
private const string _secretsEndpoint = "/secrets/";
|
||||
private const string _keysEndpoint = "/keys/";
|
||||
private const string _certificatesEndpoint = "/certificates/";
|
||||
|
||||
public AzureSecretVault(IConfigurationRoot configuration)
|
||||
public AzureSecretVault(KeyVaultConfiguration configuration)
|
||||
{
|
||||
_clientId = configuration["ClientId"];
|
||||
_secret = configuration["ClientSecret"];
|
||||
_vault = String.Format(secretUriBase, configuration.GetSection("KeyVault")["VaultName"]);
|
||||
_config = ThrowIf.Null(configuration, nameof(configuration));
|
||||
}
|
||||
|
||||
public async Task<string> Get(string secretName)
|
||||
{
|
||||
try
|
||||
{
|
||||
var secret = await GetKeyVaultClient().GetSecretAsync(_vault + secretName).ConfigureAwait(false);
|
||||
var secret = await GetKeyVaultClient().GetSecretAsync(_config.Vault + _secretsEndpoint + secretName).ConfigureAwait(false);
|
||||
return secret.Value;
|
||||
}
|
||||
catch (KeyVaultErrorException)
|
||||
catch (KeyVaultErrorException ex)
|
||||
{
|
||||
return string.Empty;
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<X509Certificate2> GetCertificate(string certificateName)
|
||||
{
|
||||
try
|
||||
{
|
||||
var cert = await GetKeyVaultClient()
|
||||
.GetCertificateAsync(_config.Vault, certificateName)
|
||||
.ConfigureAwait(false);
|
||||
return new X509Certificate2(cert.Cer);
|
||||
}
|
||||
catch (KeyVaultErrorException ex)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private async Task<string> GetToken(string authority, string resource, string scope)
|
||||
{
|
||||
var authContext = new AuthenticationContext(authority);
|
||||
ClientCredential clientCred = new ClientCredential(_clientId,
|
||||
_secret);
|
||||
ClientCredential clientCred = new ClientCredential(_config.ClientId, _config.ClientSecret);
|
||||
AuthenticationResult result = await authContext.AcquireTokenAsync(resource, clientCred);
|
||||
|
||||
if (result == null)
|
||||
|
@ -50,8 +65,6 @@ namespace Sia.Shared.Authentication
|
|||
return result.AccessToken;
|
||||
}
|
||||
|
||||
private const string secretUriBase = "https://{0}.vault.azure.net/secrets/";
|
||||
|
||||
private KeyVaultClient GetKeyVaultClient() => new KeyVaultClient(new KeyVaultClient.AuthenticationCallback(GetToken));
|
||||
|
||||
}
|
||||
|
|
|
@ -0,0 +1,29 @@
|
|||
using Sia.Shared.Validation;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Security.Cryptography.X509Certificates;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Sia.Shared.Authentication
|
||||
{
|
||||
public class KeyVaultCertificateRetriever
|
||||
: CertificateRetriever
|
||||
{
|
||||
private readonly X509Certificate2 _certificate;
|
||||
|
||||
public KeyVaultCertificateRetriever(AzureSecretVault certificateVault, string certificateName)
|
||||
{
|
||||
ThrowIf.NullOrWhiteSpace(certificateName, nameof(certificateName));
|
||||
|
||||
var certTask = certificateVault.GetCertificate(certificateName);
|
||||
Task.WaitAll(new Task[] { certTask });
|
||||
if (certTask.IsCompleted)
|
||||
{
|
||||
_certificate = certTask.Result;
|
||||
}
|
||||
}
|
||||
|
||||
public override X509Certificate2 Certificate => _certificate;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,24 @@
|
|||
using Microsoft.Extensions.Configuration;
|
||||
using Sia.Shared.Validation;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
|
||||
namespace Sia.Shared.Authentication
|
||||
{
|
||||
public class KeyVaultConfiguration
|
||||
{
|
||||
public KeyVaultConfiguration(string clientId, string clientSecret, string vault)
|
||||
{
|
||||
ClientId = ThrowIf.NullOrWhiteSpace(clientId, nameof(clientId));
|
||||
ClientSecret = ThrowIf.NullOrWhiteSpace(clientSecret, nameof(clientSecret));
|
||||
Vault = String.Format(secretUriBase, ThrowIf.NullOrWhiteSpace(vault, nameof(vault)));
|
||||
}
|
||||
|
||||
private const string secretUriBase = "https://{0}.vault.azure.net";
|
||||
|
||||
public readonly string Vault;
|
||||
public readonly string ClientId;
|
||||
public readonly string ClientSecret;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,39 @@
|
|||
using AutoMapper;
|
||||
using Newtonsoft.Json;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Dynamic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Sia.Shared.Data
|
||||
{
|
||||
|
||||
public interface IJsonDataString
|
||||
{
|
||||
string Data { get; set; }
|
||||
}
|
||||
|
||||
public interface IJsonDataObject
|
||||
{
|
||||
object Data { get; set; }
|
||||
}
|
||||
|
||||
public class ResolveJsonToString<TSource, TDestination>
|
||||
: IValueResolver<TSource, TDestination, string>
|
||||
where TSource: IJsonDataObject
|
||||
where TDestination: IJsonDataString
|
||||
{
|
||||
public string Resolve(TSource source, TDestination destination, string destMember, ResolutionContext context)
|
||||
=> source.Data is null ? null : JsonConvert.SerializeObject(source.Data);
|
||||
}
|
||||
|
||||
public class ResolveStringToJson<TSource, TDestination>
|
||||
: IValueResolver<TSource, TDestination, object>
|
||||
where TSource : IJsonDataString
|
||||
where TDestination : IJsonDataObject
|
||||
{
|
||||
public object Resolve(TSource source, TDestination destination, object destMember, ResolutionContext context)
|
||||
=> source.Data is null ? null : JsonConvert.DeserializeObject(source.Data);
|
||||
}
|
||||
}
|
|
@ -14,6 +14,7 @@
|
|||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="AutoMapper" Version="6.1.1" />
|
||||
<PackageReference Include="Microsoft.Azure.KeyVault" Version="2.3.2" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="2.0.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.Configuration" Version="2.0.0" />
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
using Sia.Domain;
|
||||
using Sia.Gateway.Initialization;
|
||||
using Sia.Gateway.Requests;
|
||||
using Sia.Gateway.ServiceRepositories;
|
||||
using Sia.Gateway.Tests.TestDoubles;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
|
@ -10,20 +10,23 @@ namespace Sia.Gateway.Tests.Requests
|
|||
[TestClass]
|
||||
public class GetEventTests
|
||||
{
|
||||
[TestInitialize]
|
||||
public void ConfigureAutomapper()
|
||||
=> AutoMapperStartup.InitializeAutomapper();
|
||||
|
||||
[TestMethod]
|
||||
public async Task Handle_WhenEventClientReturnsSuccessful_ReturnCorrectEvent()
|
||||
{
|
||||
long expectedEventId = 200;
|
||||
long expectedEventTypeId = 50;
|
||||
long expectedIncidentId = 2;
|
||||
long expectedEventId = 1;
|
||||
long expectedEventTypeId = 1;
|
||||
long expectedIncidentId = 1;
|
||||
var expectedEvent = new Event
|
||||
{
|
||||
Id = expectedEventId,
|
||||
EventTypeId = expectedEventTypeId,
|
||||
IncidentId = expectedIncidentId
|
||||
};
|
||||
IEventRepository mockRepository = new StubEventRepository(expectedEvent);
|
||||
var serviceUnderTest = new GetEventHandler(mockRepository);
|
||||
var serviceUnderTest = new GetEventHandler(MockFactory.IncidentContext("Get"));
|
||||
var request = new GetEventRequest(expectedIncidentId, expectedEventId, new DummyAuthenticatedUserContext());
|
||||
|
||||
|
||||
|
|
|
@ -1,7 +1,8 @@
|
|||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
using Sia.Connectors.Tickets.None;
|
||||
using Sia.Domain;
|
||||
using Sia.Gateway.Initialization;
|
||||
using Sia.Gateway.Requests;
|
||||
using Sia.Gateway.ServiceRepositories;
|
||||
using Sia.Gateway.Tests.TestDoubles;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
|
@ -10,18 +11,20 @@ namespace Sia.Gateway.Tests.Requests
|
|||
[TestClass]
|
||||
public class GetIncidentTests
|
||||
{
|
||||
[TestInitialize]
|
||||
public void ConfigureAutomapper()
|
||||
=> AutoMapperStartup.InitializeAutomapper();
|
||||
[TestMethod]
|
||||
public async Task Handle_WhenIncidentClientReturnsSuccessful_ReturnCorrectIncident()
|
||||
{
|
||||
long expectedIncidentId = 200;
|
||||
string expectedIncidentTitle = "The thing we were looking for";
|
||||
long expectedIncidentId = 1;
|
||||
string expectedIncidentTitle = "Customers are unable to access [REDACTED] from [REDACTED]";
|
||||
var expectedIncident = new Incident
|
||||
{
|
||||
Id = expectedIncidentId,
|
||||
Title = expectedIncidentTitle
|
||||
};
|
||||
IIncidentRepository mockClient = new StubIncidentRepository(expectedIncident, null);
|
||||
var serviceUnderTest = new GetIncidentHandler(mockClient);
|
||||
var serviceUnderTest = new GetIncidentHandler<EmptyTicket>(MockFactory.IncidentContext("Get"), new NoConnector(new NoClient(), new NoConverter()));
|
||||
var request = new GetIncidentRequest(expectedIncidentId, new DummyAuthenticatedUserContext());
|
||||
|
||||
|
||||
|
|
|
@ -0,0 +1,49 @@
|
|||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
using Sia.Domain;
|
||||
using Sia.Gateway.Initialization;
|
||||
using Sia.Gateway.Requests;
|
||||
using Sia.Gateway.Tests.TestDoubles;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Sia.Gateway.Tests.Requests
|
||||
{
|
||||
[TestClass]
|
||||
public class GetIncidentsByTicketCreateIfNeededRequestTest
|
||||
{
|
||||
[TestInitialize]
|
||||
public void ConfigureAutomapper()
|
||||
=> AutoMapperStartup.InitializeAutomapper();
|
||||
|
||||
[TestMethod]
|
||||
public async Task Handle_WhenIncidentNotExist_ReturnNewIncident()
|
||||
{
|
||||
var serviceUnderTest = new GetIncidentsByTicketCreateIfNeededRequestHandler(MockFactory.IncidentContext("Get"));
|
||||
var request = new GetIncidentsByTicketCreateIfNeededRequest("100", new DummyAuthenticatedUserContext());
|
||||
|
||||
|
||||
var result = (await serviceUnderTest.Handle(request)).ToList();
|
||||
|
||||
Assert.AreEqual(1, result.Count());
|
||||
Assert.AreEqual( "100", result[0].PrimaryTicket.OriginId);
|
||||
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public async Task Handle_WhenIncidentExists_ReturnCorrectIncidents()
|
||||
{
|
||||
|
||||
var serviceUnderTest = new GetIncidentsByTicketCreateIfNeededRequestHandler(MockFactory.IncidentContext("Get"));
|
||||
var request = new GetIncidentsByTicketCreateIfNeededRequest("44444444", new DummyAuthenticatedUserContext());
|
||||
|
||||
var result = (await serviceUnderTest.Handle(request)).ToList();
|
||||
|
||||
Assert.AreEqual(1, result.Count());
|
||||
Assert.AreEqual(1, result[0].Id);
|
||||
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,7 +1,7 @@
|
|||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
using Sia.Domain;
|
||||
using Sia.Gateway.Initialization;
|
||||
using Sia.Gateway.Requests;
|
||||
using Sia.Gateway.ServiceRepositories;
|
||||
using Sia.Gateway.Tests.TestDoubles;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
|
@ -12,11 +12,19 @@ namespace Sia.Gateway.Tests.Requests
|
|||
[TestClass]
|
||||
public class GetIncidentsTests
|
||||
{
|
||||
[TestInitialize]
|
||||
public void ConfigureAutomapper()
|
||||
=> AutoMapperStartup.InitializeAutomapper();
|
||||
|
||||
[TestMethod]
|
||||
public async Task Handle_WhenIncidentClientReturnsSuccessful_ReturnCorrectIncidents()
|
||||
{
|
||||
long[] expectedIncidentIds = { 200, 300, 400 };
|
||||
string[] expectedIncidentTitles = { "First", "Second", "Third" };
|
||||
long[] expectedIncidentIds = { 1, 2, 3 };
|
||||
string[] expectedIncidentTitles = {
|
||||
"Customers are unable to access [REDACTED] from [REDACTED]",
|
||||
"Loss of [REDACTED] Connectivity in [REDACTED]",
|
||||
"[REDACTED] and [REDACTED] service management operations for a subset of users in [REDACTED] are failing"
|
||||
};
|
||||
Incident[] expectedIncidents = new Incident[expectedIncidentIds.Length];
|
||||
for (int i = 0; i < expectedIncidents.Length; i++)
|
||||
{
|
||||
|
@ -26,8 +34,7 @@ namespace Sia.Gateway.Tests.Requests
|
|||
Title = expectedIncidentTitles[i]
|
||||
};
|
||||
}
|
||||
IIncidentRepository mockRepository = new StubIncidentRepository(expectedIncidents, null);
|
||||
var serviceUnderTest = new GetIncidentsHandler(mockRepository);
|
||||
var serviceUnderTest = new GetIncidentsHandler(MockFactory.IncidentContext("Get"));
|
||||
var request = new GetIncidentsRequest(new DummyAuthenticatedUserContext());
|
||||
|
||||
|
||||
|
|
|
@ -2,8 +2,8 @@
|
|||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
using Sia.Domain;
|
||||
using Sia.Domain.ApiModels;
|
||||
using Sia.Gateway.Initialization;
|
||||
using Sia.Gateway.Requests;
|
||||
using Sia.Gateway.ServiceRepositories;
|
||||
using Sia.Gateway.Tests.TestDoubles;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
|
@ -17,13 +17,7 @@ namespace Sia.Gateway.Tests.Requests
|
|||
|
||||
[TestInitialize]
|
||||
public void ConfigureAutomapper()
|
||||
{
|
||||
Mapper.Initialize(configuration =>
|
||||
{
|
||||
configuration.CreateMap<NewIncident, Incident>();
|
||||
});
|
||||
_mapper = Mapper.Instance;
|
||||
}
|
||||
=> AutoMapperStartup.InitializeAutomapper();
|
||||
|
||||
[TestMethod]
|
||||
public async Task Handle_WhenIncidentClientReturnsSuccessful_ReturnCorrectIncidents()
|
||||
|
@ -32,9 +26,13 @@ namespace Sia.Gateway.Tests.Requests
|
|||
var expectedIncident = new NewIncident
|
||||
{
|
||||
Title = expectedIncidentTitle,
|
||||
PrimaryTicket = new Ticket()
|
||||
{
|
||||
OriginId = "testOnlyPleaseIgnore"
|
||||
}
|
||||
};
|
||||
IIncidentRepository mockRepository = new StubIncidentRepository(new List<Incident>(), _mapper);
|
||||
var serviceUnderTest = new PostIncidentHandler(mockRepository);
|
||||
|
||||
var serviceUnderTest = new PostIncidentHandler(MockFactory.IncidentContext(nameof(Handle_WhenIncidentClientReturnsSuccessful_ReturnCorrectIncidents)));
|
||||
var request = new PostIncidentRequest(expectedIncident, new DummyAuthenticatedUserContext());
|
||||
|
||||
|
||||
|
|
|
@ -7,8 +7,8 @@
|
|||
<ItemGroup>
|
||||
<PackageReference Include="AutoMapper" Version="6.1.1" />
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="15.3.0" />
|
||||
<PackageReference Include="MSTest.TestAdapter" Version="1.1.18" />
|
||||
<PackageReference Include="MSTest.TestFramework" Version="1.1.18" />
|
||||
<PackageReference Include="MSTest.TestAdapter" Version="1.2.0" />
|
||||
<PackageReference Include="MSTest.TestFramework" Version="1.2.0" />
|
||||
<PackageReference Include="Newtonsoft.Json" Version="10.0.3" />
|
||||
</ItemGroup>
|
||||
|
||||
|
|
|
@ -0,0 +1,27 @@
|
|||
using Microsoft.EntityFrameworkCore;
|
||||
using Sia.Data.Incidents;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
|
||||
namespace Sia.Gateway.Tests.TestDoubles
|
||||
{
|
||||
public static class MockFactory
|
||||
{
|
||||
/// <summary>
|
||||
/// Returns an in-memory Incident Context with seed data
|
||||
/// </summary>
|
||||
/// <param name="instance">Name of the particular in-memory store to use. Re-use is not suggested when modifying data during test (nameof() the test method is preferred)</param>
|
||||
/// <returns></returns>
|
||||
public static IncidentContext IncidentContext(string instance)
|
||||
{
|
||||
var options = new DbContextOptionsBuilder<IncidentContext>()
|
||||
.UseInMemoryDatabase(instance)
|
||||
.Options;
|
||||
var context = new IncidentContext(options);
|
||||
SeedData.Add(context);
|
||||
|
||||
return context;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,41 +0,0 @@
|
|||
using AutoMapper;
|
||||
using Sia.Domain;
|
||||
using Sia.Domain.ApiModels;
|
||||
using Sia.Gateway.Authentication;
|
||||
using Sia.Gateway.ServiceRepositories;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Sia.Gateway.Tests.TestDoubles
|
||||
{
|
||||
public class StubEventRepository : IEventRepository
|
||||
{
|
||||
private List<Event> _events;
|
||||
|
||||
public StubEventRepository(Event ev)
|
||||
: this(new List<Event>() { ev }) { }
|
||||
public StubEventRepository(ICollection<Event> events)
|
||||
{
|
||||
_events = events.ToList();
|
||||
StatusCodeToRespondWith = HttpStatusCode.OK;
|
||||
IsSuccessStatusCodeToRespondWith = true;
|
||||
ContentToRespondWith = "You weren't going to use this anyway";
|
||||
}
|
||||
|
||||
public HttpStatusCode StatusCodeToRespondWith { get; set; }
|
||||
public bool IsSuccessStatusCodeToRespondWith { get; set; }
|
||||
public string ContentToRespondWith { get; set; }
|
||||
|
||||
public Task<Event> GetEvent(long incidentId, long id, AuthenticatedUserContext userContext)
|
||||
{
|
||||
return Task.FromResult(_events.First(ev => ev.Id == id && ev.IncidentId == incidentId));
|
||||
}
|
||||
|
||||
public Task<Event> PostEvent(long incidentId, NewEvent newEvent, AuthenticatedUserContext userContext)
|
||||
{
|
||||
return Task.FromResult(Mapper.Map(newEvent, new Event()));
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,60 +0,0 @@
|
|||
using AutoMapper;
|
||||
using Sia.Domain;
|
||||
using Sia.Domain.ApiModels;
|
||||
using Sia.Gateway.Authentication;
|
||||
using Sia.Gateway.ServiceRepositories;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Sia.Gateway.Tests.TestDoubles
|
||||
{
|
||||
public class StubIncidentRepository
|
||||
: IIncidentRepository
|
||||
|
||||
{
|
||||
private IMapper _mapper;
|
||||
|
||||
public StubIncidentRepository(Incident incident, IMapper mapper)
|
||||
: this(new List<Incident>() { incident }, mapper) { }
|
||||
public StubIncidentRepository(ICollection<Incident> incidents, IMapper mapper)
|
||||
{
|
||||
_mapper = mapper;
|
||||
_incidents = incidents.ToList();
|
||||
StatusCodeToRespondWith = HttpStatusCode.OK;
|
||||
IsSuccessStatusCodeToRespondWith = true;
|
||||
ContentToRespondWith = "You weren't going to use this anyway";
|
||||
}
|
||||
|
||||
public HttpStatusCode StatusCodeToRespondWith { get; set; }
|
||||
|
||||
public bool IsSuccessStatusCodeToRespondWith { get; set; }
|
||||
|
||||
public string ContentToRespondWith { get; set; }
|
||||
|
||||
List<Incident> _incidents { get; set; }
|
||||
|
||||
|
||||
public Task<Incident> GetIncidentAsync(long id, AuthenticatedUserContext userContext)
|
||||
{
|
||||
return Task.FromResult(_incidents.First(cr => cr.Id == id));
|
||||
}
|
||||
|
||||
public Task<IEnumerable<Incident>> GetIncidentsAsync(AuthenticatedUserContext userContext)
|
||||
{
|
||||
return Task.FromResult(_incidents.AsEnumerable());
|
||||
}
|
||||
|
||||
public Task<IEnumerable<Incident>> GetIncidentsByTicketAsync(string ticketId, AuthenticatedUserContext userContext)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public Task<Incident> PostIncidentAsync(NewIncident incident, AuthenticatedUserContext userContext)
|
||||
{
|
||||
return Task.FromResult(_mapper.Map<NewIncident, Incident>(incident));
|
||||
}
|
||||
}
|
||||
}
|
Загрузка…
Ссылка в новой задаче