diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..910cfff1 --- /dev/null +++ b/.gitignore @@ -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 diff --git a/NUIX.InvestmentPerformance.API.Test/InvestmentServiceTests.cs b/NUIX.InvestmentPerformance.API.Test/InvestmentServiceTests.cs new file mode 100644 index 00000000..70fed772 --- /dev/null +++ b/NUIX.InvestmentPerformance.API.Test/InvestmentServiceTests.cs @@ -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); + } + } +} \ No newline at end of file diff --git a/NUIX.InvestmentPerformance.API.Test/NUIX.InvestmentPerformance.API.Test.csproj b/NUIX.InvestmentPerformance.API.Test/NUIX.InvestmentPerformance.API.Test.csproj new file mode 100644 index 00000000..28a8782f --- /dev/null +++ b/NUIX.InvestmentPerformance.API.Test/NUIX.InvestmentPerformance.API.Test.csproj @@ -0,0 +1,26 @@ + + + + net8.0 + enable + enable + + false + true + + + + + + + + + + + + + + + + + diff --git a/NUIX.InvestmentPerformance.API.Test/Usings.cs b/NUIX.InvestmentPerformance.API.Test/Usings.cs new file mode 100644 index 00000000..cefced49 --- /dev/null +++ b/NUIX.InvestmentPerformance.API.Test/Usings.cs @@ -0,0 +1 @@ +global using NUnit.Framework; \ No newline at end of file diff --git a/NUIX.InvestmentPerformance.API/.vs/NUIX.InvestmentPerformance.API/DesignTimeBuild/.dtbcache.v2 b/NUIX.InvestmentPerformance.API/.vs/NUIX.InvestmentPerformance.API/DesignTimeBuild/.dtbcache.v2 new file mode 100644 index 00000000..05b31f1f Binary files /dev/null and b/NUIX.InvestmentPerformance.API/.vs/NUIX.InvestmentPerformance.API/DesignTimeBuild/.dtbcache.v2 differ diff --git a/NUIX.InvestmentPerformance.API/.vs/NUIX.InvestmentPerformance.API/FileContentIndex/2135542b-8b5c-4fd7-944f-ce7775837f66.vsidx b/NUIX.InvestmentPerformance.API/.vs/NUIX.InvestmentPerformance.API/FileContentIndex/2135542b-8b5c-4fd7-944f-ce7775837f66.vsidx new file mode 100644 index 00000000..7b872d1a Binary files /dev/null and b/NUIX.InvestmentPerformance.API/.vs/NUIX.InvestmentPerformance.API/FileContentIndex/2135542b-8b5c-4fd7-944f-ce7775837f66.vsidx differ diff --git a/NUIX.InvestmentPerformance.API/.vs/NUIX.InvestmentPerformance.API/FileContentIndex/acf8e340-f998-4880-8e8c-5921cd4fe399.vsidx b/NUIX.InvestmentPerformance.API/.vs/NUIX.InvestmentPerformance.API/FileContentIndex/acf8e340-f998-4880-8e8c-5921cd4fe399.vsidx new file mode 100644 index 00000000..3ad84263 Binary files /dev/null and b/NUIX.InvestmentPerformance.API/.vs/NUIX.InvestmentPerformance.API/FileContentIndex/acf8e340-f998-4880-8e8c-5921cd4fe399.vsidx differ diff --git a/NUIX.InvestmentPerformance.API/.vs/NUIX.InvestmentPerformance.API/FileContentIndex/afd75703-9adf-4753-b1ab-ef34bcf2a799.vsidx b/NUIX.InvestmentPerformance.API/.vs/NUIX.InvestmentPerformance.API/FileContentIndex/afd75703-9adf-4753-b1ab-ef34bcf2a799.vsidx new file mode 100644 index 00000000..39d2c0b5 Binary files /dev/null and b/NUIX.InvestmentPerformance.API/.vs/NUIX.InvestmentPerformance.API/FileContentIndex/afd75703-9adf-4753-b1ab-ef34bcf2a799.vsidx differ diff --git a/NUIX.InvestmentPerformance.API/.vs/NUIX.InvestmentPerformance.API/FileContentIndex/b010e24f-e131-489b-8030-2c397b9af43e.vsidx b/NUIX.InvestmentPerformance.API/.vs/NUIX.InvestmentPerformance.API/FileContentIndex/b010e24f-e131-489b-8030-2c397b9af43e.vsidx new file mode 100644 index 00000000..0ffba567 Binary files /dev/null and b/NUIX.InvestmentPerformance.API/.vs/NUIX.InvestmentPerformance.API/FileContentIndex/b010e24f-e131-489b-8030-2c397b9af43e.vsidx differ diff --git a/NUIX.InvestmentPerformance.API/.vs/NUIX.InvestmentPerformance.API/FileContentIndex/read.lock b/NUIX.InvestmentPerformance.API/.vs/NUIX.InvestmentPerformance.API/FileContentIndex/read.lock new file mode 100644 index 00000000..e69de29b diff --git a/NUIX.InvestmentPerformance.API/.vs/NUIX.InvestmentPerformance.API/config/applicationhost.config b/NUIX.InvestmentPerformance.API/.vs/NUIX.InvestmentPerformance.API/config/applicationhost.config new file mode 100644 index 00000000..600d7584 --- /dev/null +++ b/NUIX.InvestmentPerformance.API/.vs/NUIX.InvestmentPerformance.API/config/applicationhost.config @@ -0,0 +1,972 @@ + + + + + + +
+
+
+
+
+
+
+
+ + +
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ +
+
+ +
+
+
+
+
+
+ +
+
+
+
+
+ +
+
+
+ +
+
+ +
+
+ +
+
+
+ + +
+
+
+
+
+
+ +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/NUIX.InvestmentPerformance.API/.vs/NUIX.InvestmentPerformance.API/v17/.futdcache.v2 b/NUIX.InvestmentPerformance.API/.vs/NUIX.InvestmentPerformance.API/v17/.futdcache.v2 new file mode 100644 index 00000000..dbe38d6b Binary files /dev/null and b/NUIX.InvestmentPerformance.API/.vs/NUIX.InvestmentPerformance.API/v17/.futdcache.v2 differ diff --git a/NUIX.InvestmentPerformance.API/.vs/NUIX.InvestmentPerformance.API/v17/.suo b/NUIX.InvestmentPerformance.API/.vs/NUIX.InvestmentPerformance.API/v17/.suo new file mode 100644 index 00000000..d444f689 Binary files /dev/null and b/NUIX.InvestmentPerformance.API/.vs/NUIX.InvestmentPerformance.API/v17/.suo differ diff --git a/NUIX.InvestmentPerformance.API/.vs/ProjectEvaluation/nuix.investmentperformance.api.metadata.v6.1 b/NUIX.InvestmentPerformance.API/.vs/ProjectEvaluation/nuix.investmentperformance.api.metadata.v6.1 new file mode 100644 index 00000000..baa6fed8 Binary files /dev/null and b/NUIX.InvestmentPerformance.API/.vs/ProjectEvaluation/nuix.investmentperformance.api.metadata.v6.1 differ diff --git a/NUIX.InvestmentPerformance.API/.vs/ProjectEvaluation/nuix.investmentperformance.api.projects.v6.1 b/NUIX.InvestmentPerformance.API/.vs/ProjectEvaluation/nuix.investmentperformance.api.projects.v6.1 new file mode 100644 index 00000000..4f48feaf Binary files /dev/null and b/NUIX.InvestmentPerformance.API/.vs/ProjectEvaluation/nuix.investmentperformance.api.projects.v6.1 differ diff --git a/NUIX.InvestmentPerformance.API/Controllers/InvestmentsController.cs b/NUIX.InvestmentPerformance.API/Controllers/InvestmentsController.cs new file mode 100644 index 00000000..b2ad3b19 --- /dev/null +++ b/NUIX.InvestmentPerformance.API/Controllers/InvestmentsController.cs @@ -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> 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 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); + } + + /// + /// Adding this incase the dev team wants to see the middleware + /// + /// + /// + //[HttpGet("throw")] + //public IActionResult ThrowException() + //{ + // throw new Exception("This is a test exception"); + //} + } +} diff --git a/NUIX.InvestmentPerformance.API/Data/InvestmentDbContext.cs b/NUIX.InvestmentPerformance.API/Data/InvestmentDbContext.cs new file mode 100644 index 00000000..0d666e41 --- /dev/null +++ b/NUIX.InvestmentPerformance.API/Data/InvestmentDbContext.cs @@ -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 options) + : base(options) { } + + public DbSet Users => Set(); + public DbSet Investments => Set(); + + protected override void OnModelCreating(ModelBuilder modelBuilder) + { + base.OnModelCreating(modelBuilder); + + // Relationships + modelBuilder.Entity() + .HasMany(u => u.UserInvestments) + .WithOne() + .HasForeignKey(i => i.InvestmentID); + } + } +} diff --git a/NUIX.InvestmentPerformance.API/DataTransferObjects/InvestmentDetailDTO.cs b/NUIX.InvestmentPerformance.API/DataTransferObjects/InvestmentDetailDTO.cs new file mode 100644 index 00000000..30ad7511 --- /dev/null +++ b/NUIX.InvestmentPerformance.API/DataTransferObjects/InvestmentDetailDTO.cs @@ -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; } + } +} diff --git a/NUIX.InvestmentPerformance.API/DataTransferObjects/InvestmentSummaryDTO.cs b/NUIX.InvestmentPerformance.API/DataTransferObjects/InvestmentSummaryDTO.cs new file mode 100644 index 00000000..95049d8c --- /dev/null +++ b/NUIX.InvestmentPerformance.API/DataTransferObjects/InvestmentSummaryDTO.cs @@ -0,0 +1,8 @@ +namespace NUIX.InvestmentPerformance.API.DataTransferObjects +{ + public class InvestmentSummaryDTO + { + public Guid InvestmentID { get; set; } + public string InvestmentName { get; set; } + } +} diff --git a/NUIX.InvestmentPerformance.API/Database/Investment.bak b/NUIX.InvestmentPerformance.API/Database/Investment.bak new file mode 100644 index 00000000..116d3a2f Binary files /dev/null and b/NUIX.InvestmentPerformance.API/Database/Investment.bak differ diff --git a/NUIX.InvestmentPerformance.API/Middleware/ExceptionMiddleware.cs b/NUIX.InvestmentPerformance.API/Middleware/ExceptionMiddleware.cs new file mode 100644 index 00000000..ad53f689 --- /dev/null +++ b/NUIX.InvestmentPerformance.API/Middleware/ExceptionMiddleware.cs @@ -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 _logger; + + public ExceptionMiddleware(RequestDelegate next, ILogger 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); + } + } + } +} diff --git a/NUIX.InvestmentPerformance.API/Models/Enums/InvestmentTerm.cs b/NUIX.InvestmentPerformance.API/Models/Enums/InvestmentTerm.cs new file mode 100644 index 00000000..8fe3b813 --- /dev/null +++ b/NUIX.InvestmentPerformance.API/Models/Enums/InvestmentTerm.cs @@ -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, + } +} diff --git a/NUIX.InvestmentPerformance.API/Models/Investment.cs b/NUIX.InvestmentPerformance.API/Models/Investment.cs new file mode 100644 index 00000000..57d36a64 --- /dev/null +++ b/NUIX.InvestmentPerformance.API/Models/Investment.cs @@ -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; } + } +} diff --git a/NUIX.InvestmentPerformance.API/Models/UserInvestment.cs b/NUIX.InvestmentPerformance.API/Models/UserInvestment.cs new file mode 100644 index 00000000..915540e0 --- /dev/null +++ b/NUIX.InvestmentPerformance.API/Models/UserInvestment.cs @@ -0,0 +1,8 @@ +namespace NUIX.InvestmentPerformance.API.Models +{ + public class UserInvestment + { + public Guid UserInvestmentID { get; set; } + public List UserInvestments { get; set; } + } +} diff --git a/NUIX.InvestmentPerformance.API/NUIX.InvestmentPerformance.API.csproj b/NUIX.InvestmentPerformance.API/NUIX.InvestmentPerformance.API.csproj new file mode 100644 index 00000000..5ec50f21 --- /dev/null +++ b/NUIX.InvestmentPerformance.API/NUIX.InvestmentPerformance.API.csproj @@ -0,0 +1,26 @@ + + + + net8.0 + enable + enable + + + + + + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + + + + + + + Always + + + + diff --git a/NUIX.InvestmentPerformance.API/NUIX.InvestmentPerformance.API.csproj.user b/NUIX.InvestmentPerformance.API/NUIX.InvestmentPerformance.API.csproj.user new file mode 100644 index 00000000..031db340 --- /dev/null +++ b/NUIX.InvestmentPerformance.API/NUIX.InvestmentPerformance.API.csproj.user @@ -0,0 +1,8 @@ + + + + https + MvcControllerEmptyScaffolder + root/Common/MVC/Controller + + \ No newline at end of file diff --git a/NUIX.InvestmentPerformance.API/NUIX.InvestmentPerformance.API.sln b/NUIX.InvestmentPerformance.API/NUIX.InvestmentPerformance.API.sln new file mode 100644 index 00000000..16c471d6 --- /dev/null +++ b/NUIX.InvestmentPerformance.API/NUIX.InvestmentPerformance.API.sln @@ -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 diff --git a/NUIX.InvestmentPerformance.API/Program.cs b/NUIX.InvestmentPerformance.API/Program.cs new file mode 100644 index 00000000..4d386b1b --- /dev/null +++ b/NUIX.InvestmentPerformance.API/Program.cs @@ -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(); + +builder.Services.AddDbContext(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(); + +app.UseHttpsRedirection(); +app.UseAuthorization(); +app.MapControllers(); + +app.Run(); diff --git a/NUIX.InvestmentPerformance.API/Properties/launchSettings.json b/NUIX.InvestmentPerformance.API/Properties/launchSettings.json new file mode 100644 index 00000000..f022a94c --- /dev/null +++ b/NUIX.InvestmentPerformance.API/Properties/launchSettings.json @@ -0,0 +1,41 @@ +{ + "$schema": "https://json.schemastore.org/launchsettings.json", + "iisSettings": { + "windowsAuthentication": false, + "anonymousAuthentication": true, + "iisExpress": { + "applicationUrl": "http://localhost:2303", + "sslPort": 44353 + } + }, + "profiles": { + "http": { + "commandName": "Project", + "dotnetRunMessages": true, + "launchBrowser": true, + "launchUrl": "swagger", + "applicationUrl": "http://localhost:5217", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + }, + "https": { + "commandName": "Project", + "dotnetRunMessages": true, + "launchBrowser": true, + "launchUrl": "swagger", + "applicationUrl": "https://localhost:7257;http://localhost:5217", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + }, + "IIS Express": { + "commandName": "IISExpress", + "launchBrowser": true, + "launchUrl": "swagger", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + } + } +} diff --git a/NUIX.InvestmentPerformance.API/README.md b/NUIX.InvestmentPerformance.API/README.md new file mode 100644 index 00000000..bdbe809b --- /dev/null +++ b/NUIX.InvestmentPerformance.API/README.md @@ -0,0 +1,39 @@ +# 📊 Investment Performance API + +This is a production-based Web API built in **C# (.NET 8)** for an investment trading platform. +It allows querying of a user's investments and detailed performance data for individual investments. + +## 🚀 Features + +- Get a list of investments for a user +- Get detailed performance metrics of an individual investment +- SQL Server integration with Entity Framework Core +- Global exception handling middleware +- Unit-tested business logic + +## ✅ Requirements + +- [.NET 8 SDK](https://dotnet.microsoft.com/en-us/download) +- [SQL Server](https://www.microsoft.com/en-us/sql-server/sql-server-downloads) +- [SQL Server Management Studio (SSMS)](https://learn.microsoft.com/en-us/ssms/download-sql-server-management-studio-ssms) + +## 🗃️ Database Setup + +- Open SSMS and restore the database using the .bak file located at: /Database/InvestmentDb.bak + +## 🔐 Configuration + +- Ensure your connection string in appsettings.json looks like this or something similar to this: + +```json +"ConnectionStrings": { + "DefaultConnection": "Server=localhost;Database=InvestmentDb;Trusted_Connection=True;TrustServerCertificate=True;" +} +``` + +⚙️ Scalability Considerations +Here’s how I would scale this system if the opportunity presented itself: + +- Indexing: Ensure DB indexes on UserID, InvestmentID +- Pagination & Filtering: Add skip/take parameters to endpoints +- Command Query Responsibility Segregation (CQRS): Split read/write logic if write and read scale differently. diff --git a/NUIX.InvestmentPerformance.API/Services/IInvestmentService.cs b/NUIX.InvestmentPerformance.API/Services/IInvestmentService.cs new file mode 100644 index 00000000..ac3cc636 --- /dev/null +++ b/NUIX.InvestmentPerformance.API/Services/IInvestmentService.cs @@ -0,0 +1,10 @@ +using NUIX.InvestmentPerformance.API.DataTransferObjects; + +namespace NUIX.InvestmentPerformance.API.Services +{ + public interface IInvestmentService + { + public List GetInvestmentsForUser(Guid userId); + public InvestmentDetailDTO? GetInvestmentDetails(Guid userId, Guid investmentId); + } +} diff --git a/NUIX.InvestmentPerformance.API/Services/InvestmentService.cs b/NUIX.InvestmentPerformance.API/Services/InvestmentService.cs new file mode 100644 index 00000000..88f34add --- /dev/null +++ b/NUIX.InvestmentPerformance.API/Services/InvestmentService.cs @@ -0,0 +1,68 @@ +using NUIX.InvestmentPerformance.API.Data; +using NUIX.InvestmentPerformance.API.DataTransferObjects; +using NUIX.InvestmentPerformance.API.Models; +using NUIX.InvestmentPerformance.API.Models.Enums; +using InvestmentDbContext = NUIX.InvestmentPerformance.API.Data.InvestmentDbContext; + +namespace NUIX.InvestmentPerformance.API.Services +{ + public class InvestmentService : IInvestmentService + { + private readonly InvestmentDbContext _context; + + public InvestmentService() + { + + } + + public InvestmentService(InvestmentDbContext context) + { + _context = context; + } + + public List GetInvestmentsForUser(Guid userId) + { + return _context.Investments + .Where(i => i.UserID == userId) + .Select(i => new InvestmentSummaryDTO + { + InvestmentID = i.InvestmentID, + InvestmentName = i.InvestmentName + }) + .ToList(); + } + + public InvestmentDetailDTO? GetInvestmentDetails(Guid userId, Guid investmentId) + { + var investment = _context.Investments + .FirstOrDefault(i => i.InvestmentID == investmentId && i.UserID == userId); + + if (investment == null) + return null; + + return CalculateInvestmentDetails(investment); + } + + public InvestmentDetailDTO CalculateInvestmentDetails(Investment investment) + { + + var currentValue = investment.NumberOfShares * investment.CurrentPricePerShare; + var term = (DateTime.UtcNow - investment.PurchaseDate).TotalDays > 365 ? InvestmentTerm.LongTerm : InvestmentTerm.ShortTerm; + var totalCost = investment.NumberOfShares * investment.CostBasisPerShare; + var gainLoss = currentValue - totalCost; + + return new InvestmentDetailDTO + { + InvestmentID = investment.InvestmentID, + InvestmentName = investment.InvestmentName, + NumberOfShares = investment.NumberOfShares, + CostBasisPerShare = investment.CostBasisPerShare, + CurrentPrice = investment.CurrentPricePerShare, + CurrentValue = currentValue, + Term = term, + TotalGainOrLoss = gainLoss + }; + } + } + +} diff --git a/NUIX.InvestmentPerformance.API/appsettings.Development.json b/NUIX.InvestmentPerformance.API/appsettings.Development.json new file mode 100644 index 00000000..0c208ae9 --- /dev/null +++ b/NUIX.InvestmentPerformance.API/appsettings.Development.json @@ -0,0 +1,8 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + } +} diff --git a/NUIX.InvestmentPerformance.API/appsettings.json b/NUIX.InvestmentPerformance.API/appsettings.json new file mode 100644 index 00000000..0c208ae9 --- /dev/null +++ b/NUIX.InvestmentPerformance.API/appsettings.json @@ -0,0 +1,8 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + } +}