Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
################################################################################
# This .gitignore file was automatically created by Microsoft(R) Visual Studio.
################################################################################

/NUIX.InvestmentPerformance.API/obj
/NUIX.InvestmentPerformance.API.Test/obj
/NUIX.InvestmentPerformance.API/bin/Debug/net7.0
/NUIX.InvestmentPerformance.API/bin/Debug/net8.0
/NUIX.InvestmentPerformance.API.Test/bin/Debug
/NUIX.InvestmentPerformance.API.Test/appsettings.Development.json
/NUIX.InvestmentPerformance.API.Test/appsettings.json
/CodingExercise - Shortcut.lnk
39 changes: 39 additions & 0 deletions NUIX.InvestmentPerformance.API.Test/InvestmentServiceTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
using Microsoft.EntityFrameworkCore;
using Moq;
using NUIX.InvestmentPerformance.API.Data;
using NUIX.InvestmentPerformance.API.Models;
using NUIX.InvestmentPerformance.API.Models.Enums;
using NUIX.InvestmentPerformance.API.Services;

namespace NUIX.InvestmentPerformance.API.Test
{
public class InvestmentServiceTests
{

[TestCase( "11111111-1111-1111-1111-111111111111","CFD2EC55-EC50-4160-AADC-18DB56C2E608", "Alphabet Inc. (Google)")]
[Test]
public void GetInvestmentDetails_ShouldCalculateCorrectValues(string userID, string investmentID, string investmentName)
{
// Arrange
var investment = new Investment
{
InvestmentID = Guid.Parse(investmentID),
UserID = Guid.Parse(userID),
InvestmentName = investmentName,
NumberOfShares = 10,
CostBasisPerShare = 150m,
CurrentPricePerShare = 180m,
PurchaseDate = DateTime.Now.AddMonths(-18)
};
var service = new InvestmentService();

// Act
var result = service.CalculateInvestmentDetails(investment);

// Assert
Assert.AreEqual(1800m, result.CurrentValue);
Assert.AreEqual(InvestmentTerm.LongTerm, result.Term);
Assert.AreEqual(300m, result.TotalGainOrLoss);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>

<IsPackable>false</IsPackable>
<IsTestProject>true</IsTestProject>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Microsoft.EntityFrameworkCore.InMemory" Version="9.0.4" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.5.0" />
<PackageReference Include="Moq" Version="4.20.72" />
<PackageReference Include="NUnit" Version="3.13.3" />
<PackageReference Include="NUnit3TestAdapter" Version="4.4.2" />
<PackageReference Include="NUnit.Analyzers" Version="3.6.1" />
<PackageReference Include="coverlet.collector" Version="3.2.0" />
</ItemGroup>

<ItemGroup>
<ProjectReference Include="..\NUIX.InvestmentPerformance.API\NUIX.InvestmentPerformance.API.csproj" />
</ItemGroup>

</Project>
1 change: 1 addition & 0 deletions NUIX.InvestmentPerformance.API.Test/Usings.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
global using NUnit.Framework;
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.

Large diffs are not rendered by default.

Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
using Microsoft.AspNetCore.Mvc;
using NUIX.InvestmentPerformance.API.DataTransferObjects;
using NUIX.InvestmentPerformance.API.Services;

namespace NUIX.InvestmentPerformance.API.Controllers
{
[ApiController]
[Route("api/investments")]
public class InvestmentsController : ControllerBase
{
private readonly IInvestmentService investmentService;

public InvestmentsController(IInvestmentService investmentService)
{
this.investmentService = investmentService;
}

[HttpGet]
[Route("api/investments/get-investments")]
public ActionResult<List<InvestmentSummaryDTO>> GetInvestments(Guid userInvestmentID)
{
var investments = investmentService.GetInvestmentsForUser(userInvestmentID);

if (investments == null || investments.Count() == 0)
return NotFound("Unable to retrieve data for userInvestmentID:" + userInvestmentID + ". Please try again using a valid userInvestmentID.");

return Ok(investments);
}

[HttpGet("api/investments/get-user-investments-details")]
public ActionResult<InvestmentDetailDTO> GetUserInvestmentDetails(Guid userInvestmentID, Guid investmentId)
{
var details = investmentService.GetInvestmentDetails(userInvestmentID, investmentId);
if (details == null)
return NotFound($"Unable to retrieve Investment Details for userInvestmentID:{userInvestmentID} and investmentID:{investmentId}. Please try again using a valid userInvestmentID and investmentID.");

return Ok(details);
}

/// <summary>
/// Adding this incase the dev team wants to see the middleware
/// </summary>
/// <returns></returns>
/// <exception cref="Exception"></exception>
//[HttpGet("throw")]
//public IActionResult ThrowException()
//{
// throw new Exception("This is a test exception");
//}
}
}
32 changes: 32 additions & 0 deletions NUIX.InvestmentPerformance.API/Data/InvestmentDbContext.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
using NUIX.InvestmentPerformance.API.Models;
using System.Collections.Generic;
using System.Reflection.Emit;
using Microsoft.EntityFrameworkCore;

namespace NUIX.InvestmentPerformance.API.Data
{
public class InvestmentDbContext : DbContext
{
public InvestmentDbContext()
{

}

public InvestmentDbContext(DbContextOptions<InvestmentDbContext> options)
: base(options) { }

public DbSet<UserInvestment> Users => Set<UserInvestment>();
public DbSet<Investment> Investments => Set<Investment>();

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);

// Relationships
modelBuilder.Entity<UserInvestment>()
.HasMany(u => u.UserInvestments)
.WithOne()
.HasForeignKey(i => i.InvestmentID);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
using NUIX.InvestmentPerformance.API.Models.Enums;

namespace NUIX.InvestmentPerformance.API.DataTransferObjects
{
public class InvestmentDetailDTO
{
public Guid InvestmentID { get; set; }
public string InvestmentName { get; set; }
public int NumberOfShares { get; set; }
public decimal CostBasisPerShare { get; set; }
public decimal CurrentPrice { get; set; }
public decimal CurrentValue { get; set; }
public InvestmentTerm Term { get; set; }
public decimal TotalGainOrLoss { get; set; }
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
namespace NUIX.InvestmentPerformance.API.DataTransferObjects
{
public class InvestmentSummaryDTO
{
public Guid InvestmentID { get; set; }
public string InvestmentName { get; set; }
}
}
Binary file not shown.
41 changes: 41 additions & 0 deletions NUIX.InvestmentPerformance.API/Middleware/ExceptionMiddleware.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
using System.Net;
using System.Text.Json;

namespace NUIX.InvestmentPerformance.API.Middleware
{
public class ExceptionMiddleware
{
private readonly RequestDelegate _next;
private readonly ILogger<ExceptionMiddleware> _logger;

public ExceptionMiddleware(RequestDelegate next, ILogger<ExceptionMiddleware> logger)
{
_next = next;
_logger = logger;
}

public async Task InvokeAsync(HttpContext context)
{
try
{
await _next(context);
}
catch (Exception ex)
{
_logger.LogError(ex, "Unhandled exception occurred.");

context.Response.ContentType = "application/json";
context.Response.StatusCode = (int)HttpStatusCode.InternalServerError;

var response = new
{
StatusCode = context.Response.StatusCode,
Message = "An unexpected error occurred. Please check logs for more information."
};

var json = JsonSerializer.Serialize(response);
await context.Response.WriteAsync(json);
}
}
}
}
15 changes: 15 additions & 0 deletions NUIX.InvestmentPerformance.API/Models/Enums/InvestmentTerm.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
using System.ComponentModel;
using System.ComponentModel.DataAnnotations;
using System.Xml.Linq;

namespace NUIX.InvestmentPerformance.API.Models.Enums
{
public enum InvestmentTerm
{
[Display(Name = "Short Term")]
ShortTerm = 0,

[Display(Name = "Long Term")]
LongTerm = 1,
}
}
15 changes: 15 additions & 0 deletions NUIX.InvestmentPerformance.API/Models/Investment.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
using System.ComponentModel.DataAnnotations.Schema;

namespace NUIX.InvestmentPerformance.API.Models
{
public class Investment
{
public Guid InvestmentID { get; set; }
public Guid UserID { get; set; }
public string InvestmentName { get; set; }
public int NumberOfShares { get; set; }
public decimal CostBasisPerShare { get; set; }
public decimal CurrentPricePerShare { get; set; }
public DateTime PurchaseDate { get; set; }
}
}
8 changes: 8 additions & 0 deletions NUIX.InvestmentPerformance.API/Models/UserInvestment.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
namespace NUIX.InvestmentPerformance.API.Models
{
public class UserInvestment
{
public Guid UserInvestmentID { get; set; }
public List<Investment> UserInvestments { get; set; }
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
<Project Sdk="Microsoft.NET.Sdk.Web">

<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.OpenApi" Version="7.0.5" />
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="9.0.4" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="9.0.4">
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PrivateAssets>all</PrivateAssets>
</PackageReference>
<PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" Version="9.0.4" />
<PackageReference Include="Swashbuckle.AspNetCore" Version="6.4.0" />
</ItemGroup>

<ItemGroup>
<Content Update="appsettings.json">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
</ItemGroup>

</Project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="Current" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<ActiveDebugProfile>https</ActiveDebugProfile>
<Controller_SelectedScaffolderID>MvcControllerEmptyScaffolder</Controller_SelectedScaffolderID>
<Controller_SelectedScaffolderCategoryPath>root/Common/MVC/Controller</Controller_SelectedScaffolderCategoryPath>
</PropertyGroup>
</Project>
31 changes: 31 additions & 0 deletions NUIX.InvestmentPerformance.API/NUIX.InvestmentPerformance.API.sln
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 17
VisualStudioVersion = 17.5.33627.172
MinimumVisualStudioVersion = 10.0.40219.1
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "NUIX.InvestmentPerformance.API", "NUIX.InvestmentPerformance.API.csproj", "{7DB4EABC-11FF-48B9-BBE3-B61D33877F3C}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NUIX.InvestmentPerformance.API.Test", "..\NUIX.InvestmentPerformance.API.Test\NUIX.InvestmentPerformance.API.Test.csproj", "{29811233-607A-4DB2-AC79-08B1953CB421}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{7DB4EABC-11FF-48B9-BBE3-B61D33877F3C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{7DB4EABC-11FF-48B9-BBE3-B61D33877F3C}.Debug|Any CPU.Build.0 = Debug|Any CPU
{7DB4EABC-11FF-48B9-BBE3-B61D33877F3C}.Release|Any CPU.ActiveCfg = Release|Any CPU
{7DB4EABC-11FF-48B9-BBE3-B61D33877F3C}.Release|Any CPU.Build.0 = Release|Any CPU
{29811233-607A-4DB2-AC79-08B1953CB421}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{29811233-607A-4DB2-AC79-08B1953CB421}.Debug|Any CPU.Build.0 = Debug|Any CPU
{29811233-607A-4DB2-AC79-08B1953CB421}.Release|Any CPU.ActiveCfg = Release|Any CPU
{29811233-607A-4DB2-AC79-08B1953CB421}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {11E0EFE7-B57C-490A-9CE0-4B0DA2CE4ADE}
EndGlobalSection
EndGlobal
35 changes: 35 additions & 0 deletions NUIX.InvestmentPerformance.API/Program.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
using NUIX.InvestmentPerformance.API.Data;
using NUIX.InvestmentPerformance.API.Middleware;
using NUIX.InvestmentPerformance.API.Services;
using Microsoft.EntityFrameworkCore;


var builder = WebApplication.CreateBuilder(args);
// Add services to the container.
builder.Services.AddControllers();
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();

// Register investment service for dependency injection
builder.Services.AddScoped<IInvestmentService, InvestmentService>();

builder.Services.AddDbContext<InvestmentDbContext>(options =>
options.UseSqlServer(builder.Configuration.GetConnectionString("DefaultConnection")));

var app = builder.Build();

// Enable middleware to serve Swagger
if (app.Environment.IsDevelopment())
{
app.UseSwagger();
app.UseSwaggerUI();

}

app.UseMiddleware<ExceptionMiddleware>();

app.UseHttpsRedirection();
app.UseAuthorization();
app.MapControllers();

app.Run();
Loading