diff --git a/.github/workflows/AareonTechnicalTest20220120110002.yml b/.github/workflows/AareonTechnicalTest20220120110002.yml
new file mode 100644
index 0000000..530309e
--- /dev/null
+++ b/.github/workflows/AareonTechnicalTest20220120110002.yml
@@ -0,0 +1,76 @@
+name: Build and deploy .NET Core application to windows webapp AareonTechnicalTest20220120110002 with API Management Service AareonTechnicalTestapi
+on:
+ push:
+ branches:
+ - completed_test
+env:
+ AZURE_WEBAPP_NAME: AareonTechnicalTest20220120110002
+ DOTNET_CORE_VERSION: 6.0.x
+ WORKING_DIRECTORY: AareonTechnicalTest
+ CONFIGURATION: Release
+ AZURE_WEBAPP_PACKAGE_PATH: AareonTechnicalTest/publish
+ AZURE_APIM_RESOURCE_PATH: /
+ AZURE_APIM_RESOURCEGROUP: AareonTechnicalTest20220120105445ResourceGroup
+ AZURE_APIM_SERVICENAME: AareonTechnicalTestapi
+ AZURE_APIM_API_ID: AareonTechnicalTest
+ AZURE_APIM_APPSERVICEURL: https://aareontechnicaltest20220120110002.azurewebsites.net
+ SWASHBUCLE_ASPNET_CORE_CLI_PACKAGE_VERSION: 5.6.3
+ SWASHBUCKLE_DOTNET_CORE_VERSION: 3.1.x
+ API_IMPORT_SPECIFICATION_PATH: AareonTechnicalTest/publish/swagger.json
+ API_IMPORT_DLL: AareonTechnicalTest/publish/AareonTechnicalTest.dll
+ API_IMPORT_VERSION: v1
+jobs:
+ build:
+ runs-on: windows-latest
+ steps:
+ - uses: actions/checkout@v2
+ - name: Setup .NET Core
+ uses: actions/setup-dotnet@v1
+ with:
+ dotnet-version: ${{ env.DOTNET_CORE_VERSION }}
+ - name: Setup SwashBuckle .NET Core
+ uses: actions/setup-dotnet@v1
+ with:
+ dotnet-version: ${{ env.SWASHBUCKLE_DOTNET_CORE_VERSION }}
+ - name: Restore
+ run: dotnet restore ${{ env.WORKING_DIRECTORY }}
+ - name: Build
+ run: dotnet build ${{ env.WORKING_DIRECTORY }} --configuration ${{ env.CONFIGURATION }} --no-restore
+ - name: Test
+ run: dotnet test ${{ env.WORKING_DIRECTORY }} --no-build
+ - name: Publish
+ run: dotnet publish ${{ env.WORKING_DIRECTORY }} --configuration ${{ env.CONFIGURATION }} --no-build --output ${{ env.AZURE_WEBAPP_PACKAGE_PATH }}
+ - name: Install Swashbuckle CLI .NET Global Tool
+ run: dotnet tool install --global Swashbuckle.AspNetCore.Cli --version ${{ env.SWASHBUCLE_ASPNET_CORE_CLI_PACKAGE_VERSION }}
+ working-directory: ${{ env.WORKING_DIRECTORY }}
+ - name: Generate Open API Specification Document
+ run: swagger tofile --output "${{ env.API_IMPORT_SPECIFICATION_PATH }}" "${{ env.API_IMPORT_DLL }}" "${{ env.API_IMPORT_VERSION }}"
+ - name: Publish Artifacts
+ uses: actions/upload-artifact@v1.0.0
+ with:
+ name: webapp
+ path: ${{ env.AZURE_WEBAPP_PACKAGE_PATH }}
+ deploy:
+ runs-on: windows-latest
+ needs: build
+ steps:
+ - name: Download artifact from build job
+ uses: actions/download-artifact@v2
+ with:
+ name: webapp
+ path: ${{ env.AZURE_WEBAPP_PACKAGE_PATH }}
+ - name: Deploy to Azure WebApp
+ uses: azure/webapps-deploy@v2
+ with:
+ app-name: ${{ env.AZURE_WEBAPP_NAME }}
+ package: ${{ env.AZURE_WEBAPP_PACKAGE_PATH }}
+ publish-profile: ${{ secrets.AareonTechnicalTest20220120110002_4333 }}
+ - name: Azure Login
+ uses: azure/login@v1
+ with:
+ creds: ${{ secrets.AareonTechnicalTestapi_spn }}
+ - name: Import API into Azure API Management
+ run: az apim api import --path "${{ env.AZURE_APIM_RESOURCE_PATH }}" --resource-group "${{ env.AZURE_APIM_RESOURCEGROUP }}" --service-name "${{ env.AZURE_APIM_SERVICENAME }}" --api-id "${{ env.AZURE_APIM_API_ID }}" --service-url "${{ env.AZURE_APIM_APPSERVICEURL }}" --specification-path "${{ env.API_IMPORT_SPECIFICATION_PATH }}" --specification-format OpenApi --subscription-required false
+ - name: logout
+ run: >
+ az logout
diff --git a/AareonTechnicalTest.Tests/AareonTechnicalTest.Tests.csproj b/AareonTechnicalTest.Tests/AareonTechnicalTest.Tests.csproj
new file mode 100644
index 0000000..760d523
--- /dev/null
+++ b/AareonTechnicalTest.Tests/AareonTechnicalTest.Tests.csproj
@@ -0,0 +1,24 @@
+
+
+
+ net6.0
+ enable
+
+ false
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/AareonTechnicalTest.Tests/NotesServiceShould.cs b/AareonTechnicalTest.Tests/NotesServiceShould.cs
new file mode 100644
index 0000000..d63e32b
--- /dev/null
+++ b/AareonTechnicalTest.Tests/NotesServiceShould.cs
@@ -0,0 +1,102 @@
+using AareonTechnicalTest.Models;
+using AareonTechnicalTest.Repositories;
+using AareonTechnicalTest.Services;
+using FluentAssertions;
+using Microsoft.AspNetCore.Mvc;
+using Microsoft.VisualStudio.TestTools.UnitTesting;
+using NSubstitute;
+using System.Threading.Tasks;
+
+namespace AareonTechnicalTest.Tests
+{
+ [TestClass]
+ public class NotesServiceShould
+ {
+ private INoteRepository? _noteRepository;
+ private NoteService? _noteService;
+ private IPersonRepository? _personRepository;
+
+ [TestInitialize]
+ public void Initialise()
+ {
+ _noteRepository = Substitute.For();
+ _personRepository = Substitute.For();
+
+ _noteService = new NoteService(_noteRepository, _personRepository);
+ }
+
+ [TestMethod]
+ public async Task FailDeleteNoteWhenNoteNotFound()
+ {
+ var personId = 1;
+ var noteId = 1;
+
+ Note? noteMock = null;
+ _noteRepository!.FindNoteAsync(noteId).Returns(noteMock);
+
+ var response = await _noteService!.DeleteNoteAsync(personId, noteId);
+ var x = response
+ .Should()
+ .BeOfType();
+ }
+
+ [TestMethod]
+ public async Task FailDeleteNoteWhenPersonNotFound()
+ {
+ var personId = 1;
+ var noteId = 1;
+
+ Note? noteMock = new Note { Id = noteId };
+ _noteRepository!.FindNoteAsync(noteId).Returns(noteMock);
+
+ Person? personMock = null;
+ _personRepository!.FindPersonAsync(noteId).Returns(personMock);
+
+ var response = await _noteService!.DeleteNoteAsync(personId, noteId);
+ var x = response
+ .Should()
+ .BeOfType();
+ }
+
+ [TestMethod]
+ public async Task FailDeleteNoteWhenPersonIsNotAdmin()
+ {
+ var personId = 1;
+ var noteId = 1;
+
+ Note? noteMock = new Note { Id = noteId };
+ _noteRepository!.FindNoteAsync(noteId).Returns(noteMock);
+
+ Person? personMock = new Person { Id = personId, IsAdmin = false };
+ _personRepository!.FindPersonAsync(noteId).Returns(personMock);
+
+ var response = await _noteService!.DeleteNoteAsync(personId, noteId);
+ var x = response
+ .Should()
+ .BeOfType();
+
+ var problemDetails = x.Subject.Value.Should().BeOfType().Which;
+
+ problemDetails.Detail.Should().Be($"The person with ID {personId} is not an administrator.", because: "The person was not an administrator");
+ }
+
+
+ [TestMethod]
+ public async Task DeleteNoteWhenPersonIsAdmin()
+ {
+ var personId = 1;
+ var noteId = 1;
+
+ Note? noteMock = new Note { Id = noteId };
+ _noteRepository!.FindNoteAsync(noteId).Returns(noteMock);
+
+ Person? personMock = new Person { Id = personId, IsAdmin = true };
+ _personRepository!.FindPersonAsync(noteId).Returns(personMock);
+
+ var response = await _noteService!.DeleteNoteAsync(personId, noteId);
+ var x = response
+ .Should()
+ .BeOfType();
+ }
+ }
+}
\ No newline at end of file
diff --git a/AareonTechnicalTest.sln b/AareonTechnicalTest.sln
index c3ca477..22e7dc9 100644
--- a/AareonTechnicalTest.sln
+++ b/AareonTechnicalTest.sln
@@ -1,9 +1,11 @@
Microsoft Visual Studio Solution File, Format Version 12.00
-# Visual Studio Version 16
-VisualStudioVersion = 16.0.31624.102
+# Visual Studio Version 17
+VisualStudioVersion = 17.0.32014.148
MinimumVisualStudioVersion = 10.0.40219.1
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AareonTechnicalTest", "AareonTechnicalTest\AareonTechnicalTest.csproj", "{1FB831BF-946F-4526-AB25-E5CAFC6A8E4F}"
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AareonTechnicalTest", "AareonTechnicalTest\AareonTechnicalTest.csproj", "{1FB831BF-946F-4526-AB25-E5CAFC6A8E4F}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AareonTechnicalTest.Tests", "AareonTechnicalTest.Tests\AareonTechnicalTest.Tests.csproj", "{E46C0B0C-69A8-432B-9ABB-086E0B62740A}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
@@ -15,6 +17,10 @@ Global
{1FB831BF-946F-4526-AB25-E5CAFC6A8E4F}.Debug|Any CPU.Build.0 = Debug|Any CPU
{1FB831BF-946F-4526-AB25-E5CAFC6A8E4F}.Release|Any CPU.ActiveCfg = Release|Any CPU
{1FB831BF-946F-4526-AB25-E5CAFC6A8E4F}.Release|Any CPU.Build.0 = Release|Any CPU
+ {E46C0B0C-69A8-432B-9ABB-086E0B62740A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {E46C0B0C-69A8-432B-9ABB-086E0B62740A}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {E46C0B0C-69A8-432B-9ABB-086E0B62740A}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {E46C0B0C-69A8-432B-9ABB-086E0B62740A}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
diff --git a/AareonTechnicalTest/AareonTechnicalTest.csproj b/AareonTechnicalTest/AareonTechnicalTest.csproj
index 601dd80..5f6c11a 100644
--- a/AareonTechnicalTest/AareonTechnicalTest.csproj
+++ b/AareonTechnicalTest/AareonTechnicalTest.csproj
@@ -1,18 +1,23 @@
- net5.0
+ net6.0
-
-
-
-
+
+
+
+
+
+
+
all
runtime; build; native; contentfiles; analyzers; buildtransitive
-
+
+
+
diff --git a/AareonTechnicalTest/ApplicationContext.cs b/AareonTechnicalTest/ApplicationContext.cs
index 2218ef2..13f61fb 100644
--- a/AareonTechnicalTest/ApplicationContext.cs
+++ b/AareonTechnicalTest/ApplicationContext.cs
@@ -18,6 +18,8 @@ public ApplicationContext(DbContextOptions options)
public virtual DbSet Tickets { get; set; }
+ public virtual DbSet Notes { get; set; }
+
public string DatabasePath { get; set; }
protected override void OnConfiguring(DbContextOptionsBuilder options)
@@ -29,6 +31,7 @@ protected override void OnModelCreating(ModelBuilder modelBuilder)
{
PersonConfig.Configure(modelBuilder);
TicketConfig.Configure(modelBuilder);
+ NoteConfig.Configure(modelBuilder);
}
}
}
diff --git a/AareonTechnicalTest/Controllers/NotesController.cs b/AareonTechnicalTest/Controllers/NotesController.cs
new file mode 100644
index 0000000..c324942
--- /dev/null
+++ b/AareonTechnicalTest/Controllers/NotesController.cs
@@ -0,0 +1,50 @@
+using AareonTechnicalTest.Models;
+using AareonTechnicalTest.Services;
+using Microsoft.AspNetCore.Mvc;
+using System.Collections.Generic;
+using System.Threading.Tasks;
+
+namespace AareonTechnicalTest.Controllers
+{
+ [Route("api/[controller]")]
+ [ApiController]
+ public class NotesController : ControllerBase
+ {
+ private readonly INoteService _noteService;
+
+ public NotesController(INoteService noteService)
+ {
+ _noteService = noteService;
+ }
+
+ [HttpGet]
+ public async Task>> GetNotes()
+ {
+ return await _noteService.GetNotesAsync();
+ }
+
+ [HttpGet("{id}")]
+ public async Task> GetNote(int id)
+ {
+ return await _noteService.GetNoteAsync(id);
+ }
+
+ [HttpPut("{id}")]
+ public async Task PutNote(int id, Note note)
+ {
+ return await _noteService.PutNoteAsync(id, note);
+ }
+
+ [HttpPost]
+ public async Task> PostNote(Note note)
+ {
+ return await _noteService.PostNoteAsync(note);
+ }
+
+ [HttpDelete("{id}")]
+ public async Task DeleteNote(int personId, int id)
+ {
+ return await _noteService.DeleteNoteAsync(personId, id);
+ }
+ }
+}
diff --git a/AareonTechnicalTest/Controllers/TicketsController.cs b/AareonTechnicalTest/Controllers/TicketsController.cs
new file mode 100644
index 0000000..58b68c7
--- /dev/null
+++ b/AareonTechnicalTest/Controllers/TicketsController.cs
@@ -0,0 +1,50 @@
+using AareonTechnicalTest.Models;
+using AareonTechnicalTest.Services;
+using Microsoft.AspNetCore.Mvc;
+using System.Collections.Generic;
+using System.Threading.Tasks;
+
+namespace AareonTechnicalTest.Controllers
+{
+ [Route("api/[controller]")]
+ [ApiController]
+ public class TicketsController : ControllerBase
+ {
+ private readonly ITicketService _ticketService;
+
+ public TicketsController(ITicketService ticketService)
+ {
+ _ticketService = ticketService;
+ }
+
+ [HttpGet]
+ public async Task>> GetTickets()
+ {
+ return await _ticketService.GetTicketsAsync();
+ }
+
+ [HttpGet("{id}")]
+ public async Task> GetTicket(int id)
+ {
+ return await _ticketService.GetTicketAsync(id);
+ }
+
+ [HttpPut("{id}")]
+ public async Task PutTicket(int id, Ticket ticket)
+ {
+ return await _ticketService.PutTicketAsync(id, ticket);
+ }
+
+ [HttpPost]
+ public async Task> PostTicket(Ticket ticket)
+ {
+ return await _ticketService.PostTicketAsync(ticket);
+ }
+
+ [HttpDelete("{id}")]
+ public async Task DeleteTicket(int id)
+ {
+ return await _ticketService.DeleteTicketAsync(id);
+ }
+ }
+}
diff --git a/AareonTechnicalTest/Migrations/20220119201548_AddNotes.Designer.cs b/AareonTechnicalTest/Migrations/20220119201548_AddNotes.Designer.cs
new file mode 100644
index 0000000..68275ed
--- /dev/null
+++ b/AareonTechnicalTest/Migrations/20220119201548_AddNotes.Designer.cs
@@ -0,0 +1,80 @@
+//
+using AareonTechnicalTest;
+using Microsoft.EntityFrameworkCore;
+using Microsoft.EntityFrameworkCore.Infrastructure;
+using Microsoft.EntityFrameworkCore.Migrations;
+using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
+
+#nullable disable
+
+namespace AareonTechnicalTest.Migrations
+{
+ [DbContext(typeof(ApplicationContext))]
+ [Migration("20220119201548_AddNotes")]
+ partial class AddNotes
+ {
+ protected override void BuildTargetModel(ModelBuilder modelBuilder)
+ {
+#pragma warning disable 612, 618
+ modelBuilder.HasAnnotation("ProductVersion", "6.0.1");
+
+ modelBuilder.Entity("AareonTechnicalTest.Models.Note", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("INTEGER");
+
+ b.Property("Description")
+ .HasColumnType("TEXT");
+
+ b.Property("PersonId")
+ .HasColumnType("INTEGER");
+
+ b.Property("TicketId")
+ .HasColumnType("INTEGER");
+
+ b.HasKey("Id");
+
+ b.ToTable("Notes");
+ });
+
+ modelBuilder.Entity("AareonTechnicalTest.Models.Person", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("INTEGER");
+
+ b.Property("Forename")
+ .HasColumnType("TEXT");
+
+ b.Property("IsAdmin")
+ .HasColumnType("INTEGER");
+
+ b.Property("Surname")
+ .HasColumnType("TEXT");
+
+ b.HasKey("Id");
+
+ b.ToTable("Persons");
+ });
+
+ modelBuilder.Entity("AareonTechnicalTest.Models.Ticket", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("INTEGER");
+
+ b.Property("Content")
+ .HasColumnType("TEXT");
+
+ b.Property("PersonId")
+ .HasColumnType("INTEGER");
+
+ b.HasKey("Id");
+
+ b.ToTable("Tickets");
+ });
+#pragma warning restore 612, 618
+ }
+ }
+}
diff --git a/AareonTechnicalTest/Migrations/20220119201548_AddNotes.cs b/AareonTechnicalTest/Migrations/20220119201548_AddNotes.cs
new file mode 100644
index 0000000..3d96f22
--- /dev/null
+++ b/AareonTechnicalTest/Migrations/20220119201548_AddNotes.cs
@@ -0,0 +1,33 @@
+using Microsoft.EntityFrameworkCore.Migrations;
+
+#nullable disable
+
+namespace AareonTechnicalTest.Migrations
+{
+ public partial class AddNotes : Migration
+ {
+ protected override void Up(MigrationBuilder migrationBuilder)
+ {
+ migrationBuilder.CreateTable(
+ name: "Notes",
+ columns: table => new
+ {
+ Id = table.Column(type: "INTEGER", nullable: false)
+ .Annotation("Sqlite:Autoincrement", true),
+ Description = table.Column(type: "TEXT", nullable: true),
+ PersonId = table.Column(type: "INTEGER", nullable: false),
+ TicketId = table.Column(type: "INTEGER", nullable: false)
+ },
+ constraints: table =>
+ {
+ table.PrimaryKey("PK_Notes", x => x.Id);
+ });
+ }
+
+ protected override void Down(MigrationBuilder migrationBuilder)
+ {
+ migrationBuilder.DropTable(
+ name: "Notes");
+ }
+ }
+}
diff --git a/AareonTechnicalTest/Migrations/ApplicationContextModelSnapshot.cs b/AareonTechnicalTest/Migrations/ApplicationContextModelSnapshot.cs
index 4d48a1b..4153b28 100644
--- a/AareonTechnicalTest/Migrations/ApplicationContextModelSnapshot.cs
+++ b/AareonTechnicalTest/Migrations/ApplicationContextModelSnapshot.cs
@@ -1,6 +1,10 @@
//
+using AareonTechnicalTest;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
+using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
+
+#nullable disable
namespace AareonTechnicalTest.Migrations
{
@@ -10,8 +14,27 @@ partial class ApplicationContextModelSnapshot : ModelSnapshot
protected override void BuildModel(ModelBuilder modelBuilder)
{
#pragma warning disable 612, 618
- modelBuilder
- .HasAnnotation("ProductVersion", "5.0.11");
+ modelBuilder.HasAnnotation("ProductVersion", "6.0.1");
+
+ modelBuilder.Entity("AareonTechnicalTest.Models.Note", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("INTEGER");
+
+ b.Property("Description")
+ .HasColumnType("TEXT");
+
+ b.Property("PersonId")
+ .HasColumnType("INTEGER");
+
+ b.Property("TicketId")
+ .HasColumnType("INTEGER");
+
+ b.HasKey("Id");
+
+ b.ToTable("Notes");
+ });
modelBuilder.Entity("AareonTechnicalTest.Models.Person", b =>
{
diff --git a/AareonTechnicalTest/Models/Note.cs b/AareonTechnicalTest/Models/Note.cs
new file mode 100644
index 0000000..02c9e9a
--- /dev/null
+++ b/AareonTechnicalTest/Models/Note.cs
@@ -0,0 +1,16 @@
+using System.ComponentModel.DataAnnotations;
+
+namespace AareonTechnicalTest.Models
+{
+ public class Note
+ {
+ [Key]
+ public int Id { get; set; }
+
+ public string Description { get; set; }
+
+ public int PersonId { get; set; }
+
+ public int TicketId { get; set; }
+ }
+}
diff --git a/AareonTechnicalTest/Models/NoteConfig.cs b/AareonTechnicalTest/Models/NoteConfig.cs
new file mode 100644
index 0000000..356662c
--- /dev/null
+++ b/AareonTechnicalTest/Models/NoteConfig.cs
@@ -0,0 +1,22 @@
+using Microsoft.EntityFrameworkCore;
+
+namespace AareonTechnicalTest.Models
+{
+ public static class NoteConfig
+ {
+ public static void Configure(ModelBuilder modelBuilder)
+ {
+ modelBuilder.Entity(
+ entity =>
+ {
+ entity.HasKey(e => e.Id);
+ });
+
+ modelBuilder.Entity(
+ entity =>
+ {
+ entity.HasKey(e => e.Id);
+ });
+ }
+ }
+}
\ No newline at end of file
diff --git a/AareonTechnicalTest/Models/Person.cs b/AareonTechnicalTest/Models/Person.cs
index 3797241..50a846c 100644
--- a/AareonTechnicalTest/Models/Person.cs
+++ b/AareonTechnicalTest/Models/Person.cs
@@ -5,7 +5,7 @@ namespace AareonTechnicalTest.Models
public class Person
{
[Key]
- public int Id { get; }
+ public int Id { get; set; }
public string Forename { get; set; }
diff --git a/AareonTechnicalTest/Models/PersonConfig.cs b/AareonTechnicalTest/Models/PersonConfig.cs
index a2ed728..602858d 100644
--- a/AareonTechnicalTest/Models/PersonConfig.cs
+++ b/AareonTechnicalTest/Models/PersonConfig.cs
@@ -11,6 +11,12 @@ public static void Configure(ModelBuilder modelBuilder)
{
entity.HasKey(e => e.Id);
});
+
+ modelBuilder.Entity(
+ entity =>
+ {
+ entity.HasKey(e => e.Id);
+ });
}
}
}
\ No newline at end of file
diff --git a/AareonTechnicalTest/Models/TicketConfig.cs b/AareonTechnicalTest/Models/TicketConfig.cs
index 0f5b6b0..25bc4c2 100644
--- a/AareonTechnicalTest/Models/TicketConfig.cs
+++ b/AareonTechnicalTest/Models/TicketConfig.cs
@@ -11,6 +11,12 @@ public static void Configure(ModelBuilder modelBuilder)
{
entity.HasKey(e => e.Id);
});
+
+ modelBuilder.Entity(
+ entity =>
+ {
+ entity.HasKey(e => e.Id);
+ });
}
}
}
diff --git a/AareonTechnicalTest/Properties/ServiceDependencies/AareonTechnicalTest20220120110002/apis1.arm.json b/AareonTechnicalTest/Properties/ServiceDependencies/AareonTechnicalTest20220120110002/apis1.arm.json
new file mode 100644
index 0000000..c77c522
--- /dev/null
+++ b/AareonTechnicalTest/Properties/ServiceDependencies/AareonTechnicalTest20220120110002/apis1.arm.json
@@ -0,0 +1,131 @@
+{
+ "$schema": "https://schema.management.azure.com/schemas/2018-05-01/subscriptionDeploymentTemplate.json#",
+ "contentVersion": "1.0.0.0",
+ "parameters": {
+ "resourceGroupName": {
+ "type": "string",
+ "defaultValue": "AareonTechnicalTest20220120105445ResourceGroup",
+ "metadata": {
+ "_parameterType": "resourceGroup",
+ "description": "Name of the resource group for the resource. It is recommended to put resources under same resource group for better tracking."
+ }
+ },
+ "resourceGroupLocation": {
+ "type": "string",
+ "defaultValue": "southcentralus",
+ "metadata": {
+ "_parameterType": "location",
+ "description": "Location of the resource group. Resource groups could have different location than resources."
+ }
+ },
+ "resourceLocation": {
+ "type": "string",
+ "defaultValue": "[parameters('resourceGroupLocation')]",
+ "metadata": {
+ "_parameterType": "location",
+ "description": "Location of the resource. By default use resource group's location, unless the resource provider is not supported there."
+ }
+ }
+ },
+ "resources": [
+ {
+ "type": "Microsoft.Resources/resourceGroups",
+ "name": "[parameters('resourceGroupName')]",
+ "location": "[parameters('resourceGroupLocation')]",
+ "apiVersion": "2019-10-01"
+ },
+ {
+ "type": "Microsoft.Resources/deployments",
+ "name": "[concat(parameters('resourceGroupName'), 'Deployment', uniqueString(concat('AareonTechnicalTest', subscription().subscriptionId)))]",
+ "resourceGroup": "[parameters('resourceGroupName')]",
+ "apiVersion": "2019-10-01",
+ "dependsOn": [
+ "[parameters('resourceGroupName')]"
+ ],
+ "properties": {
+ "mode": "Incremental",
+ "template": {
+ "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#",
+ "contentVersion": "1.0.0.0",
+ "resources": [
+ {
+ "name": "AareonTechnicalTestapi",
+ "type": "Microsoft.ApiManagement/service",
+ "location": "[parameters('resourceLocation')]",
+ "properties": {
+ "publisherEmail": "mark@mbsoftwareconsulting.co.uk",
+ "publisherName": "Mark Bonner",
+ "notificationSenderEmail": "apimgmt-noreply@mail.windowsazure.com",
+ "hostnameConfigurations": [
+ {
+ "type": "Proxy",
+ "hostName": "aareontechnicaltestapi.azure-api.net",
+ "encodedCertificate": null,
+ "keyVaultId": null,
+ "certificatePassword": null,
+ "negotiateClientCertificate": false,
+ "certificate": null,
+ "defaultSslBinding": true
+ }
+ ],
+ "publicIPAddresses": null,
+ "privateIPAddresses": null,
+ "additionalLocations": null,
+ "virtualNetworkConfiguration": null,
+ "customProperties": {
+ "Microsoft.WindowsAzure.ApiManagement.Gateway.Security.Protocols.Tls10": "False",
+ "Microsoft.WindowsAzure.ApiManagement.Gateway.Security.Protocols.Tls11": "False",
+ "Microsoft.WindowsAzure.ApiManagement.Gateway.Security.Backend.Protocols.Tls10": "False",
+ "Microsoft.WindowsAzure.ApiManagement.Gateway.Security.Backend.Protocols.Tls11": "False",
+ "Microsoft.WindowsAzure.ApiManagement.Gateway.Security.Backend.Protocols.Ssl30": "False",
+ "Microsoft.WindowsAzure.ApiManagement.Gateway.Protocols.Server.Http2": "False"
+ },
+ "virtualNetworkType": "None",
+ "certificates": null,
+ "apiVersionConstraint": {
+ "minApiVersion": null
+ }
+ },
+ "sku": {
+ "name": "Consumption",
+ "capacity": 0
+ },
+ "apiVersion": "2019-12-01"
+ },
+ {
+ "type": "Microsoft.ApiManagement/service/apis",
+ "name": "AareonTechnicalTestapi/AareonTechnicalTest",
+ "properties": {
+ "displayName": "AareonTechnicalTest",
+ "apiRevision": "1",
+ "description": null,
+ "subscriptionRequired": true,
+ "serviceUrl": null,
+ "path": "",
+ "protocols": [
+ "https"
+ ],
+ "authenticationSettings": {
+ "oAuth2": null,
+ "openid": null
+ },
+ "subscriptionKeyParameterNames": {
+ "header": "Ocp-Apim-Subscription-Key",
+ "query": "subscription-key"
+ },
+ "isCurrent": true
+ },
+ "apiVersion": "2019-12-01",
+ "dependsOn": [
+ "AareonTechnicalTestapi"
+ ]
+ }
+ ]
+ }
+ }
+ }
+ ],
+ "metadata": {
+ "_dependencyType": "apis.azure"
+ }
+}
\ No newline at end of file
diff --git a/AareonTechnicalTest/Properties/ServiceDependencies/AareonTechnicalTest202201201100021/apis1.arm.json b/AareonTechnicalTest/Properties/ServiceDependencies/AareonTechnicalTest202201201100021/apis1.arm.json
new file mode 100644
index 0000000..24580ca
--- /dev/null
+++ b/AareonTechnicalTest/Properties/ServiceDependencies/AareonTechnicalTest202201201100021/apis1.arm.json
@@ -0,0 +1,132 @@
+{
+ "$schema": "https://schema.management.azure.com/schemas/2018-05-01/subscriptionDeploymentTemplate.json#",
+ "contentVersion": "1.0.0.0",
+ "parameters": {
+ "resourceGroupName": {
+ "type": "string",
+ "defaultValue": "AareonTechnicalTest20220120105445ResourceGroup",
+ "metadata": {
+ "_parameterType": "resourceGroup",
+ "description": "Name of the resource group for the resource. It is recommended to put resources under same resource group for better tracking."
+ }
+ },
+ "resourceGroupLocation": {
+ "type": "string",
+ "defaultValue": "southcentralus",
+ "metadata": {
+ "_parameterType": "location",
+ "description": "Location of the resource group. Resource groups could have different location than resources."
+ }
+ },
+ "resourceLocation": {
+ "type": "string",
+ "defaultValue": "[parameters('resourceGroupLocation')]",
+ "metadata": {
+ "_parameterType": "location",
+ "description": "Location of the resource. By default use resource group's location, unless the resource provider is not supported there."
+ }
+ }
+ },
+ "resources": [
+ {
+ "type": "Microsoft.Resources/resourceGroups",
+ "name": "[parameters('resourceGroupName')]",
+ "location": "[parameters('resourceGroupLocation')]",
+ "apiVersion": "2019-10-01"
+ },
+ {
+ "type": "Microsoft.Resources/deployments",
+ "name": "[concat(parameters('resourceGroupName'), 'Deployment', uniqueString(concat('AareonTechnicalTest', subscription().subscriptionId)))]",
+ "resourceGroup": "[parameters('resourceGroupName')]",
+ "apiVersion": "2019-10-01",
+ "dependsOn": [
+ "[parameters('resourceGroupName')]"
+ ],
+ "properties": {
+ "mode": "Incremental",
+ "template": {
+ "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#",
+ "contentVersion": "1.0.0.0",
+ "resources": [
+ {
+ "name": "AareonTechnicalTestapi",
+ "type": "Microsoft.ApiManagement/service",
+ "location": "[parameters('resourceLocation')]",
+ "properties": {
+ "publisherEmail": "mark@mbsoftwareconsulting.co.uk",
+ "publisherName": "Mark Bonner",
+ "notificationSenderEmail": "apimgmt-noreply@mail.windowsazure.com",
+ "hostnameConfigurations": [
+ {
+ "type": "Proxy",
+ "hostName": "aareontechnicaltestapi.azure-api.net",
+ "encodedCertificate": null,
+ "keyVaultId": null,
+ "certificatePassword": null,
+ "negotiateClientCertificate": false,
+ "certificate": null,
+ "defaultSslBinding": true
+ }
+ ],
+ "publicIPAddresses": null,
+ "privateIPAddresses": null,
+ "additionalLocations": null,
+ "virtualNetworkConfiguration": null,
+ "customProperties": {
+ "Microsoft.WindowsAzure.ApiManagement.Gateway.Security.Protocols.Tls10": "False",
+ "Microsoft.WindowsAzure.ApiManagement.Gateway.Security.Protocols.Tls11": "False",
+ "Microsoft.WindowsAzure.ApiManagement.Gateway.Security.Backend.Protocols.Tls10": "False",
+ "Microsoft.WindowsAzure.ApiManagement.Gateway.Security.Backend.Protocols.Tls11": "False",
+ "Microsoft.WindowsAzure.ApiManagement.Gateway.Security.Backend.Protocols.Ssl30": "False",
+ "Microsoft.WindowsAzure.ApiManagement.Gateway.Protocols.Server.Http2": "False"
+ },
+ "virtualNetworkType": "None",
+ "certificates": null,
+ "enableClientCertificate": true,
+ "apiVersionConstraint": {
+ "minApiVersion": null
+ }
+ },
+ "sku": {
+ "name": "Consumption",
+ "capacity": 0
+ },
+ "apiVersion": "2019-12-01"
+ },
+ {
+ "type": "Microsoft.ApiManagement/service/apis",
+ "name": "AareonTechnicalTestapi/AareonTechnicalTest",
+ "properties": {
+ "displayName": "AareonTechnicalTest",
+ "apiRevision": "1",
+ "description": null,
+ "subscriptionRequired": true,
+ "serviceUrl": null,
+ "path": "",
+ "protocols": [
+ "https"
+ ],
+ "authenticationSettings": {
+ "oAuth2": null,
+ "openid": null
+ },
+ "subscriptionKeyParameterNames": {
+ "header": "Ocp-Apim-Subscription-Key",
+ "query": "subscription-key"
+ },
+ "isCurrent": true
+ },
+ "apiVersion": "2019-12-01",
+ "dependsOn": [
+ "AareonTechnicalTestapi"
+ ]
+ }
+ ]
+ }
+ }
+ }
+ ],
+ "metadata": {
+ "_dependencyType": "apis.azure"
+ }
+}
\ No newline at end of file
diff --git a/AareonTechnicalTest/Properties/serviceDependencies.AareonTechnicalTest20220120110002.json b/AareonTechnicalTest/Properties/serviceDependencies.AareonTechnicalTest20220120110002.json
new file mode 100644
index 0000000..a512a76
--- /dev/null
+++ b/AareonTechnicalTest/Properties/serviceDependencies.AareonTechnicalTest20220120110002.json
@@ -0,0 +1,8 @@
+{
+ "dependencies": {
+ "apis1": {
+ "resourceId": "/subscriptions/[parameters('subscriptionId')]/resourceGroups/[parameters('resourceGroupName')]/providers/Microsoft.ApiManagement/service/AareonTechnicalTestapi/apis/AareonTechnicalTest",
+ "type": "apis.azure"
+ }
+ }
+}
\ No newline at end of file
diff --git a/AareonTechnicalTest/Properties/serviceDependencies.AareonTechnicalTest202201201100021.json b/AareonTechnicalTest/Properties/serviceDependencies.AareonTechnicalTest202201201100021.json
new file mode 100644
index 0000000..a512a76
--- /dev/null
+++ b/AareonTechnicalTest/Properties/serviceDependencies.AareonTechnicalTest202201201100021.json
@@ -0,0 +1,8 @@
+{
+ "dependencies": {
+ "apis1": {
+ "resourceId": "/subscriptions/[parameters('subscriptionId')]/resourceGroups/[parameters('resourceGroupName')]/providers/Microsoft.ApiManagement/service/AareonTechnicalTestapi/apis/AareonTechnicalTest",
+ "type": "apis.azure"
+ }
+ }
+}
\ No newline at end of file
diff --git a/AareonTechnicalTest/Properties/serviceDependencies.json b/AareonTechnicalTest/Properties/serviceDependencies.json
new file mode 100644
index 0000000..e32266d
--- /dev/null
+++ b/AareonTechnicalTest/Properties/serviceDependencies.json
@@ -0,0 +1,7 @@
+{
+ "dependencies": {
+ "apis1": {
+ "type": "apis"
+ }
+ }
+}
\ No newline at end of file
diff --git a/AareonTechnicalTest/Repositories/INoteRepository.cs b/AareonTechnicalTest/Repositories/INoteRepository.cs
new file mode 100644
index 0000000..834bc91
--- /dev/null
+++ b/AareonTechnicalTest/Repositories/INoteRepository.cs
@@ -0,0 +1,21 @@
+using AareonTechnicalTest.Models;
+using System.Collections.Generic;
+using System.Threading.Tasks;
+
+namespace AareonTechnicalTest.Repositories
+{
+ public interface INoteRepository
+ {
+ Task> LoadNotesAsync();
+
+ Task FindNoteAsync(int id);
+
+ Task UpdateNoteAsync(int id, Note note);
+
+ Task NoteExistsAsync(int id);
+
+ Task AddNoteAsync(Note note);
+
+ Task RemoveNoteAsync(Note note);
+ }
+}
\ No newline at end of file
diff --git a/AareonTechnicalTest/Repositories/IPersonRepository.cs b/AareonTechnicalTest/Repositories/IPersonRepository.cs
new file mode 100644
index 0000000..56c2137
--- /dev/null
+++ b/AareonTechnicalTest/Repositories/IPersonRepository.cs
@@ -0,0 +1,10 @@
+using AareonTechnicalTest.Models;
+using System.Threading.Tasks;
+
+namespace AareonTechnicalTest.Repositories
+{
+ public interface IPersonRepository
+ {
+ Task FindPersonAsync(int id);
+ }
+}
\ No newline at end of file
diff --git a/AareonTechnicalTest/Repositories/ITicketRepository.cs b/AareonTechnicalTest/Repositories/ITicketRepository.cs
new file mode 100644
index 0000000..6137945
--- /dev/null
+++ b/AareonTechnicalTest/Repositories/ITicketRepository.cs
@@ -0,0 +1,21 @@
+using AareonTechnicalTest.Models;
+using System.Collections.Generic;
+using System.Threading.Tasks;
+
+namespace AareonTechnicalTest.Repositories
+{
+ public interface ITicketRepository
+ {
+ Task> LoadTicketsAsync();
+
+ Task FindTicketAsync(int id);
+
+ Task UpdateTicketAsync(int id, Ticket ticket);
+
+ Task TicketExistsAsync(int id);
+
+ Task AddTicketAsync(Ticket ticket);
+
+ Task RemoveTicketAsync(Ticket ticket);
+ }
+}
\ No newline at end of file
diff --git a/AareonTechnicalTest/Repositories/NoteRepository.cs b/AareonTechnicalTest/Repositories/NoteRepository.cs
new file mode 100644
index 0000000..f5c2cc6
--- /dev/null
+++ b/AareonTechnicalTest/Repositories/NoteRepository.cs
@@ -0,0 +1,50 @@
+using AareonTechnicalTest.Models;
+using Microsoft.EntityFrameworkCore;
+using System.Collections.Generic;
+using System.Threading.Tasks;
+
+namespace AareonTechnicalTest.Repositories
+{
+ public class NoteRepository : INoteRepository
+ {
+ private readonly ApplicationContext _context;
+
+ public NoteRepository(ApplicationContext context)
+ {
+ _context = context;
+ }
+
+ public async Task> LoadNotesAsync()
+ {
+ return await _context.Notes.ToListAsync();
+ }
+
+ public async Task FindNoteAsync(int id)
+ {
+ return await _context.Notes.FindAsync(id);
+ }
+
+ public async Task UpdateNoteAsync(int id, Note note)
+ {
+ _context.Entry(note).State = EntityState.Modified;
+ await _context.SaveChangesAsync();
+ }
+
+ public async Task NoteExistsAsync(int id)
+ {
+ return await _context.Notes.AnyAsync(e => e.Id == id);
+ }
+
+ public async Task AddNoteAsync(Note note)
+ {
+ _context.Notes.Add(note);
+ await _context.SaveChangesAsync();
+ }
+
+ public async Task RemoveNoteAsync(Note note)
+ {
+ _context.Notes.Remove(note);
+ await _context.SaveChangesAsync();
+ }
+ }
+}
diff --git a/AareonTechnicalTest/Repositories/PersonRepository.cs b/AareonTechnicalTest/Repositories/PersonRepository.cs
new file mode 100644
index 0000000..1773ea1
--- /dev/null
+++ b/AareonTechnicalTest/Repositories/PersonRepository.cs
@@ -0,0 +1,20 @@
+using AareonTechnicalTest.Models;
+using System.Threading.Tasks;
+
+namespace AareonTechnicalTest.Repositories
+{
+ public class PersonRepository : IPersonRepository
+ {
+ private readonly ApplicationContext _context;
+
+ public PersonRepository(ApplicationContext context)
+ {
+ _context = context;
+ }
+
+ public async Task FindPersonAsync(int id)
+ {
+ return await _context.Persons.FindAsync(id);
+ }
+ }
+}
diff --git a/AareonTechnicalTest/Repositories/TicketRepository.cs b/AareonTechnicalTest/Repositories/TicketRepository.cs
new file mode 100644
index 0000000..8036120
--- /dev/null
+++ b/AareonTechnicalTest/Repositories/TicketRepository.cs
@@ -0,0 +1,50 @@
+using AareonTechnicalTest.Models;
+using Microsoft.EntityFrameworkCore;
+using System.Collections.Generic;
+using System.Threading.Tasks;
+
+namespace AareonTechnicalTest.Repositories
+{
+ public class TicketRepository : ITicketRepository
+ {
+ private readonly ApplicationContext _context;
+
+ public TicketRepository(ApplicationContext context)
+ {
+ _context = context;
+ }
+
+ public async Task AddTicketAsync(Ticket ticket)
+ {
+ _context.Tickets.Add(ticket);
+ await _context.SaveChangesAsync();
+ }
+
+ public async Task FindTicketAsync(int id)
+ {
+ return await _context.Tickets.FindAsync(id);
+ }
+
+ public async Task> LoadTicketsAsync()
+ {
+ return await _context.Tickets.ToListAsync();
+ }
+
+ public async Task RemoveTicketAsync(Ticket ticket)
+ {
+ _context.Tickets.Remove(ticket);
+ await _context.SaveChangesAsync();
+ }
+
+ public async Task TicketExistsAsync(int id)
+ {
+ return await _context.Tickets.AnyAsync(e => e.Id == id);
+ }
+
+ public async Task UpdateTicketAsync(int id, Ticket ticket)
+ {
+ _context.Entry(ticket).State = EntityState.Modified;
+ await _context.SaveChangesAsync();
+ }
+ }
+}
diff --git a/AareonTechnicalTest/Services/INoteService.cs b/AareonTechnicalTest/Services/INoteService.cs
new file mode 100644
index 0000000..708cf0d
--- /dev/null
+++ b/AareonTechnicalTest/Services/INoteService.cs
@@ -0,0 +1,20 @@
+using AareonTechnicalTest.Models;
+using Microsoft.AspNetCore.Mvc;
+using System.Collections.Generic;
+using System.Threading.Tasks;
+
+namespace AareonTechnicalTest.Services
+{
+ public interface INoteService
+ {
+ Task>> GetNotesAsync();
+
+ Task> GetNoteAsync(int id);
+
+ Task PutNoteAsync(int id, Note note);
+
+ Task> PostNoteAsync(Note note);
+
+ Task DeleteNoteAsync(int personId, int id);
+ }
+}
\ No newline at end of file
diff --git a/AareonTechnicalTest/Services/ITicketService.cs b/AareonTechnicalTest/Services/ITicketService.cs
new file mode 100644
index 0000000..4eb3a1c
--- /dev/null
+++ b/AareonTechnicalTest/Services/ITicketService.cs
@@ -0,0 +1,20 @@
+using AareonTechnicalTest.Models;
+using Microsoft.AspNetCore.Mvc;
+using System.Collections.Generic;
+using System.Threading.Tasks;
+
+namespace AareonTechnicalTest.Services
+{
+ public interface ITicketService
+ {
+ Task>> GetTicketsAsync();
+
+ Task> GetTicketAsync(int id);
+
+ Task PutTicketAsync(int id, Ticket ticket);
+
+ Task> PostTicketAsync(Ticket ticket);
+
+ Task DeleteTicketAsync(int id);
+ }
+}
\ No newline at end of file
diff --git a/AareonTechnicalTest/Services/NoteService.cs b/AareonTechnicalTest/Services/NoteService.cs
new file mode 100644
index 0000000..6f37d3e
--- /dev/null
+++ b/AareonTechnicalTest/Services/NoteService.cs
@@ -0,0 +1,130 @@
+using AareonTechnicalTest.Models;
+using AareonTechnicalTest.Repositories;
+using Audit.Core;
+using Microsoft.AspNetCore.Mvc;
+using StackExchange.Profiling;
+using System;
+using System.Collections.Generic;
+using System.Threading.Tasks;
+
+namespace AareonTechnicalTest.Services
+{
+ public class NoteService : INoteService
+ {
+ private readonly INoteRepository _noteRepository;
+ private readonly IPersonRepository _personRepository;
+
+ public NoteService(INoteRepository noteRepository, IPersonRepository personRepository)
+ {
+ _noteRepository = noteRepository;
+ _personRepository = personRepository;
+ }
+
+ public async Task>> GetNotesAsync()
+ {
+ using (MiniProfiler.Current.Step(nameof(GetNotesAsync)))
+ {
+ var notes = await _noteRepository.LoadNotesAsync();
+ var scope = AuditScope.Create($"NoteService:{GetNotesAsync}", () => notes);
+
+ return new OkObjectResult(notes);
+ }
+ }
+
+ public async Task> GetNoteAsync(int id)
+ {
+ using (MiniProfiler.Current.Step(nameof(GetNoteAsync)))
+ {
+ var note = await _noteRepository.FindNoteAsync(id);
+ var scope = AuditScope.Create($"Note:{GetNoteAsync}", () => note);
+
+ if (note is null)
+ {
+ return new NotFoundResult();
+ }
+
+ return note;
+ }
+ }
+
+ public async Task PutNoteAsync(int id, Note note)
+ {
+ using (MiniProfiler.Current.Step(nameof(PutNoteAsync)))
+ {
+ if (id != note.Id)
+ {
+ return new BadRequestResult();
+ }
+
+ try
+ {
+ await _noteRepository.UpdateNoteAsync(id, note);
+ var scope = AuditScope.Create($"Note:{PutNoteAsync}", () => note);
+ }
+ catch (Exception ex)
+ {
+ if (!await _noteRepository.NoteExistsAsync(id))
+ {
+ return new NotFoundResult();
+ }
+ else
+ {
+ return new UnprocessableEntityObjectResult(new ProblemDetails
+ {
+ Title = "Note update could not be processed.",
+ Detail = $"{ex.Message}"
+ });
+ }
+ }
+
+ return new NoContentResult();
+ }
+ }
+
+ public async Task> PostNoteAsync(Note note)
+ {
+ using (MiniProfiler.Current.Step(nameof(PostNoteAsync)))
+ {
+ await _noteRepository.AddNoteAsync(note);
+ var scope = AuditScope.Create($"Note:{PostNoteAsync}", () => note);
+
+ return new OkObjectResult(note);
+ }
+ }
+
+ public async Task DeleteNoteAsync(int personId, int id)
+ {
+ using (MiniProfiler.Current.Step(nameof(DeleteNoteAsync)))
+ {
+ var note = await _noteRepository.FindNoteAsync(id);
+ if (note is null)
+ {
+ return new NotFoundResult();
+ }
+
+ var person = await _personRepository.FindPersonAsync(personId);
+ if (person is null)
+ {
+ return new UnprocessableEntityObjectResult(new ProblemDetails
+ {
+ Title = "Note deletion could not be processed.",
+ Detail = $"The person with ID {personId} could not be found."
+ });
+ }
+ else if (!person.IsAdmin)
+ {
+ return new UnprocessableEntityObjectResult(new ProblemDetails
+ {
+ Title = "Note deletion could not be processed.",
+ Detail = $"The person with ID {personId} is not an administrator."
+ });
+ }
+
+ await _noteRepository.RemoveNoteAsync(note);
+ var scope = AuditScope.Create($"Note:{DeleteNoteAsync}", () => note);
+
+ return new NoContentResult();
+ }
+ }
+ }
+}
diff --git a/AareonTechnicalTest/Services/TicketService.cs b/AareonTechnicalTest/Services/TicketService.cs
new file mode 100644
index 0000000..8f911d6
--- /dev/null
+++ b/AareonTechnicalTest/Services/TicketService.cs
@@ -0,0 +1,110 @@
+using AareonTechnicalTest.Models;
+using AareonTechnicalTest.Repositories;
+using Audit.Core;
+using Microsoft.AspNetCore.Mvc;
+using StackExchange.Profiling;
+using System;
+using System.Collections.Generic;
+using System.Threading.Tasks;
+
+namespace AareonTechnicalTest.Services
+{
+ public class TicketService : ITicketService
+ {
+ private readonly ITicketRepository _ticketRepository;
+
+ public TicketService(ITicketRepository ticketRepository)
+ {
+ _ticketRepository = ticketRepository;
+ }
+
+ public async Task>> GetTicketsAsync()
+ {
+ using (MiniProfiler.Current.Step(nameof(GetTicketsAsync)))
+ {
+ var tickets = await _ticketRepository.LoadTicketsAsync();
+ var scope = AuditScope.Create($"Ticket:{GetTicketsAsync}", () => tickets);
+
+ return new OkObjectResult(tickets);
+ }
+ }
+
+ public async Task> GetTicketAsync(int id)
+ {
+ using (MiniProfiler.Current.Step(nameof(GetTicketAsync)))
+ {
+ var note = await _ticketRepository.FindTicketAsync(id);
+ var scope = AuditScope.Create($"Ticket:{GetTicketAsync}", () => note);
+
+ if (note is null)
+ {
+ return new NotFoundResult();
+ }
+
+ return note;
+ }
+ }
+
+ public async Task PutTicketAsync(int id, Ticket ticket)
+ {
+ using (MiniProfiler.Current.Step(nameof(PutTicketAsync)))
+ {
+ if (id != ticket.Id)
+ {
+ return new BadRequestResult();
+ }
+
+ try
+ {
+ await _ticketRepository.UpdateTicketAsync(id, ticket);
+ var scope = AuditScope.Create($"Ticket:{PutTicketAsync}", () => ticket);
+ }
+ catch (Exception ex)
+ {
+ if (!await _ticketRepository.TicketExistsAsync(id))
+ {
+ return new NotFoundResult();
+ }
+ else
+ {
+ return new UnprocessableEntityObjectResult(new ProblemDetails
+ {
+ Title = "Ticket update could not be processed.",
+ Detail = $"{ex.Message}"
+ });
+ }
+ }
+
+ return new NoContentResult();
+ }
+ }
+
+ public async Task> PostTicketAsync(Ticket ticket)
+ {
+ using (MiniProfiler.Current.Step(nameof(PostTicketAsync)))
+ {
+ await _ticketRepository.AddTicketAsync(ticket);
+ var scope = AuditScope.Create($"Ticket:{PostTicketAsync}", () => ticket);
+
+ return new OkObjectResult(ticket);
+ }
+ }
+
+ public async Task DeleteTicketAsync(int id)
+ {
+ using (MiniProfiler.Current.Step(nameof(DeleteTicketAsync)))
+ {
+ var ticket = await _ticketRepository.FindTicketAsync(id);
+ if (ticket is null)
+ {
+ return new NotFoundResult();
+ }
+
+ await _ticketRepository.RemoveTicketAsync(ticket);
+ var scope = AuditScope.Create($"Ticket:{DeleteTicketAsync}", () => ticket);
+
+ return new NoContentResult();
+ }
+ }
+ }
+}
diff --git a/AareonTechnicalTest/Startup.cs b/AareonTechnicalTest/Startup.cs
index 7b12082..c1cf75c 100644
--- a/AareonTechnicalTest/Startup.cs
+++ b/AareonTechnicalTest/Startup.cs
@@ -1,3 +1,5 @@
+using AareonTechnicalTest.Repositories;
+using AareonTechnicalTest.Services;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.EntityFrameworkCore;
@@ -20,6 +22,11 @@ public Startup(IConfiguration configuration)
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
+ services.AddTransient();
+ services.AddTransient();
+ services.AddTransient();
+ services.AddTransient();
+ services.AddTransient();
services.AddControllers();
services.AddDbContext(c => c.UseSqlite());
@@ -39,6 +46,8 @@ public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
app.UseSwaggerUI(c => c.SwaggerEndpoint("/swagger/v1/swagger.json", "AareonTechnicalTest v1"));
}
+ app.UseMiniProfiler();
+
app.UseHttpsRedirection();
app.UseRouting();
diff --git a/AareonTechnicalTest/Ticketing.db b/AareonTechnicalTest/Ticketing.db
index 63aad03..8b16d79 100644
Binary files a/AareonTechnicalTest/Ticketing.db and b/AareonTechnicalTest/Ticketing.db differ