diff --git a/.gitignore b/.gitignore
new file mode 100644
index 00000000..84adbdbb
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,135 @@
+## Ignore Visual Studio temporary files, build results, and
+## files generated by popular Visual Studio add-ons.
+
+# User-specific files
+*.suo
+*.user
+*.sln.docstates
+
+# Build results
+
+[Dd]ebug/
+[Rr]elease/
+x64/
+[Bb]in/
+[Oo]bj/
+
+# MSTest test Results
+[Tt]est[Rr]esult*/
+[Bb]uild[Ll]og.*
+
+.vs/
+
+*_i.c
+*_p.c
+*_i.h
+*.ilk
+*.meta
+*.obj
+*.pch
+*.pdb
+*.pgc
+*.pgd
+*.rsp
+*.sbr
+*.tlb
+*.tli
+*.tlh
+*.tmp
+*.tmp_proj
+*.log
+*.vspscc
+*.vssscc
+.builds
+*.pidb
+*.log
+*.svclog
+*.scc
+
+# Visual C++ cache files
+ipch/
+*.aps
+*.ncb
+*.opensdf
+*.sdf
+*.cachefile
+
+# Visual Studio profiler
+*.psess
+*.vsp
+*.vspx
+
+# Guidance Automation Toolkit
+*.gpState
+
+# ReSharper is a .NET coding add-in
+_ReSharper*/
+*.[Rr]e[Ss]harper
+*.DotSettings.user
+
+# Click-Once directory
+publish/
+
+# Publish Web Output
+*.Publish.xml
+*.pubxml
+*.azurePubxml
+
+# NuGet Packages Directory
+## TODO: If you have NuGet Package Restore enabled, uncomment the next line
+packages/
+## TODO: If the tool you use requires repositories.config, also uncomment the next line
+!packages/repositories.config
+
+# Windows Azure Build Output
+csx/
+*.build.csdef
+
+# Windows Store app package directory
+AppPackages/
+
+# Others
+sql/
+*.Cache
+ClientBin/
+[Ss]tyle[Cc]op.*
+![Ss]tyle[Cc]op.targets
+~$*
+*~
+*.dbmdl
+*.[Pp]ublish.xml
+
+*.publishsettings
+
+# RIA/Silverlight projects
+Generated_Code/
+
+# Backup & report files from converting an old project file to a newer
+# Visual Studio version. Backup files are not needed, because we have git ;-)
+_UpgradeReport_Files/
+Backup*/
+UpgradeLog*.XML
+UpgradeLog*.htm
+
+# SQL Server files
+App_Data/*.mdf
+App_Data/*.ldf
+
+# =========================
+# Windows detritus
+# =========================
+
+# Windows image file caches
+Thumbs.db
+ehthumbs.db
+
+# Folder config file
+Desktop.ini
+
+# Recycle Bin used on file shares
+$RECYCLE.BIN/
+
+# Mac desktop service store files
+.DS_Store
+
+_NCrunch*
\ No newline at end of file
diff --git a/InvestmentPerformanceWebAPI/Controllers/TransactionsController.cs b/InvestmentPerformanceWebAPI/Controllers/TransactionsController.cs
new file mode 100644
index 00000000..81e7d0cd
--- /dev/null
+++ b/InvestmentPerformanceWebAPI/Controllers/TransactionsController.cs
@@ -0,0 +1,93 @@
+using InvestmentPerformanceWebAPI.Database;
+using InvestmentPerformanceWebAPI.DataTransferObjects;
+using Microsoft.AspNetCore.Mvc;
+using Microsoft.EntityFrameworkCore;
+
+namespace InvestmentPerformanceWebAPI.Controllers
+{
+ ///
+ /// Controller responsible for handling transaction-related API endpoints.
+ /// Provides access to transaction data including basic transaction information and detailed financial calculations.
+ ///
+ [ApiController]
+ [Route("[controller]")]
+ public class TransactionsController : ControllerBase
+ {
+ private readonly ApplicationDbContext _context;
+
+ ///
+ /// Initializes a new instance of the TransactionsController.
+ ///
+ /// The Entity Framework database context used to access transaction data.
+ public TransactionsController(ApplicationDbContext context)
+ {
+ _context = context;
+ }
+
+ ///
+ /// Retrieves all transactions associated with a specific user.
+ /// Returns basic transaction information (ID and Name) for all transactions belonging to the specified user.
+ ///
+ /// The unique identifier of the user whose transactions should be retrieved.
+ [HttpGet]
+ [Route("user/{id}")]
+ public IActionResult GetTransactionsForUser(int id)
+ {
+ // Query the database for all transactions belonging to the specified user
+ // Project the results to TransactionDTO to return only basic information
+ var transactions = _context.Transactions.Where(t => t.UserId == id).Select( t => new TransactionDTO()
+ {
+ Id = t.Id,
+ Name = t.Name
+ });
+
+ return Ok(transactions);
+ }
+
+ ///
+ /// Retrieves basic information for a specific transaction by its ID.
+ /// Returns only the transaction ID and company name.
+ ///
+ /// The unique identifier of the transaction to retrieve.
+ [HttpGet]
+ [Route("{id}")]
+ public IActionResult GetTransaction(int id)
+ {
+ // Query for a specific transaction by ID and project to basic DTO
+ // Note: Returns IQueryable which may contain 0 or 1 results
+ var transactions = _context.Transactions.Where(t => t.Id == id).Select(t => new TransactionDTO()
+ {
+ Id = t.Id,
+ Name = t.Name,
+ });
+
+ return Ok(transactions);
+ }
+
+ ///
+ /// Retrieves comprehensive transaction details including financial calculations for a specific transaction.
+ /// Returns detailed information including current market value, total gain/loss, and investment performance metrics.
+ ///
+ /// The unique identifier of the transaction to retrieve detailed information for.
+ [HttpGet]
+ [Route("transaction-details/{id}")]
+ public IActionResult GetTransactionDetails(int id)
+ {
+ // Query for specific transaction and project to detailed DTO with financial calculations
+ var transactions = _context.Transactions.Where(t => t.Id == id).Select(t => new TransactionDetailsDTO()
+ {
+ Id = t.Id,
+ Name = t.Name,
+ Shares = t.Quantity, // Map Quantity to Shares for DTO
+ TransactionTime = t.TransactionTime,
+ CostBasisPerShare = t.SharePriceAtPurchase, // Original purchase price per share
+ CurrentValue = t.CurrentSharePrice * t.Quantity, // Total current market value
+ CurrentPrice = t.CurrentSharePrice, // Current market price per share
+ // Calculate total gain/loss: (Current Value) - (Original Investment)
+ TotalGain = (t.CurrentSharePrice * t.Quantity) - (t.SharePriceAtPurchase * t.Quantity)
+ });
+
+ return Ok(transactions);
+ }
+ }
+}
diff --git a/InvestmentPerformanceWebAPI/Controllers/UsersController.cs b/InvestmentPerformanceWebAPI/Controllers/UsersController.cs
new file mode 100644
index 00000000..d73cada3
--- /dev/null
+++ b/InvestmentPerformanceWebAPI/Controllers/UsersController.cs
@@ -0,0 +1,88 @@
+using InvestmentPerformanceWebAPI.Database;
+using InvestmentPerformanceWebAPI.DataTransferObjects;
+using InvestmentPerformanceWebAPI.Models;
+using Microsoft.AspNetCore.Identity;
+using Microsoft.AspNetCore.Mvc;
+using Microsoft.EntityFrameworkCore;
+using Microsoft.Extensions.Options;
+
+namespace InvestmentPerformanceWebAPI.Controllers
+{
+
+ [ApiController]
+ [Route("[controller]")]
+ public class UserController : ControllerBase
+ {
+ private readonly ApplicationDbContext _context;
+
+ ///
+ /// Initializes a new instance of the UserController.
+ ///
+ /// Database context for accessing user and transaction data.
+ public UserController(ApplicationDbContext context)
+ {
+ _context = context;
+ }
+
+ ///
+ /// Retrieves all users with basic information (ID and username only).
+ ///
+ [HttpGet]
+ public IActionResult GetUsers()
+ {
+ // Query all users and project to basic UserDTO
+ var users = _context.Users.Select(u => new UserDTO()
+ {
+ Id = u.Id,
+ Username = u.Username
+ });
+
+ // Note: This null check is unnecessary as LINQ Select never returns null
+ if (users == null)
+ {
+ return BadRequest();
+ }
+ else
+ {
+ return Ok(users);
+ }
+ }
+
+ ///
+ /// Retrieves detailed user information including complete investment portfolio with financial calculations.
+ ///
+ /// The unique identifier of the user to retrieve.
+ [HttpGet]
+ [Route("{id}")]
+ public IActionResult GetUser(int id)
+ {
+ // Query specific user with transactions, calculate financial metrics in projection
+ var user = _context.Users.Include("Transactions").Where(u => u.Id == id).Select(u => new UserDTO()
+ {
+ Id = u.Id,
+ Username = u.Username,
+ Transactions = u.Transactions.Select(t => new TransactionDetailsDTO()
+ {
+ Id = t.Id,
+ Name = t.Name,
+ Shares = t.Quantity,
+ TransactionTime = t.TransactionTime,
+ CostBasisPerShare = t.SharePriceAtPurchase,
+ CurrentValue = t.CurrentSharePrice * t.Quantity,
+ CurrentPrice = t.CurrentSharePrice,
+ TotalGain = (t.CurrentSharePrice * t.Quantity) - (t.SharePriceAtPurchase * t.Quantity)
+ }).ToList()
+ }).FirstOrDefault();
+
+ // return bad request if user not found
+ if (user == null)
+ {
+ return BadRequest();
+ }
+ else
+ {
+ return Ok(user);
+ }
+ }
+ }
+}
diff --git a/InvestmentPerformanceWebAPI/DataTransferObjects/TransactionDTO.cs b/InvestmentPerformanceWebAPI/DataTransferObjects/TransactionDTO.cs
new file mode 100644
index 00000000..6f298bb3
--- /dev/null
+++ b/InvestmentPerformanceWebAPI/DataTransferObjects/TransactionDTO.cs
@@ -0,0 +1,8 @@
+namespace InvestmentPerformanceWebAPI.DataTransferObjects
+{
+ public class TransactionDTO
+ {
+ public int Id { get; set; }
+ public string Name { get; set; } // e.g., "Microsoft"
+ }
+}
diff --git a/InvestmentPerformanceWebAPI/DataTransferObjects/TransactionDetailsDTO.cs b/InvestmentPerformanceWebAPI/DataTransferObjects/TransactionDetailsDTO.cs
new file mode 100644
index 00000000..9b9c1ec2
--- /dev/null
+++ b/InvestmentPerformanceWebAPI/DataTransferObjects/TransactionDetailsDTO.cs
@@ -0,0 +1,22 @@
+namespace InvestmentPerformanceWebAPI.DataTransferObjects
+{
+ public class TransactionDetailsDTO : TransactionDTO
+ {
+ public DateTime TransactionTime { get; set; }
+ public int Shares { get; set; }
+ public string Term
+ {
+ get
+ {
+ return TransactionTime >= DateTime.UtcNow.AddYears(-1) ? "Short-Term" : "Long-Term";
+ }
+ }
+ public double CostBasisPerShare { get; set; }
+
+ public double CurrentValue {get; set;}
+
+ public double CurrentPrice { get; set; }
+
+ public double TotalGain { get; set; }
+ }
+}
diff --git a/InvestmentPerformanceWebAPI/DataTransferObjects/UserDTO.cs b/InvestmentPerformanceWebAPI/DataTransferObjects/UserDTO.cs
new file mode 100644
index 00000000..2ca56a10
--- /dev/null
+++ b/InvestmentPerformanceWebAPI/DataTransferObjects/UserDTO.cs
@@ -0,0 +1,10 @@
+namespace InvestmentPerformanceWebAPI.DataTransferObjects
+{
+ public class UserDTO
+ {
+ public int Id { get; set; }
+ public string Username { get; set; }
+
+ public ICollection Transactions { get; set; } = new List();
+ }
+}
diff --git a/InvestmentPerformanceWebAPI/Database Models/Transaction.cs b/InvestmentPerformanceWebAPI/Database Models/Transaction.cs
new file mode 100644
index 00000000..b1a0a438
--- /dev/null
+++ b/InvestmentPerformanceWebAPI/Database Models/Transaction.cs
@@ -0,0 +1,24 @@
+using System.ComponentModel.DataAnnotations.Schema;
+
+namespace InvestmentPerformanceWebAPI.Models
+{
+ public class Transaction
+ {
+ public int Id { get; set; }
+ public TransactionType Type { get; set; }
+ public string Name { get; set; } // e.g., "Microsoft"
+ public string Symbol { get; set; } // e.g., "IBM"
+ public int Quantity { get; set; }
+ public double SharePriceAtPurchase { get; set; }
+ public double CurrentSharePrice { get; set; }
+ public DateTime TransactionTime { get; set; }
+ [ForeignKey("User")]
+ public int UserId { get; set; } // Foreign key to User
+ }
+
+ public enum TransactionType
+ {
+ Buy,
+ Sell
+ }
+}
diff --git a/InvestmentPerformanceWebAPI/Database Models/User.cs b/InvestmentPerformanceWebAPI/Database Models/User.cs
new file mode 100644
index 00000000..43c4cba5
--- /dev/null
+++ b/InvestmentPerformanceWebAPI/Database Models/User.cs
@@ -0,0 +1,12 @@
+using System.Reflection.Metadata;
+
+namespace InvestmentPerformanceWebAPI.Models
+{
+ public class User
+ {
+ public int Id { get; set; }
+ public string Username { get; set; }
+ public string Email { get; set; }
+ public ICollection Transactions { get; set; } = new List();
+ }
+}
\ No newline at end of file
diff --git a/InvestmentPerformanceWebAPI/Database/ApplicationDbContext.cs b/InvestmentPerformanceWebAPI/Database/ApplicationDbContext.cs
new file mode 100644
index 00000000..1ce60311
--- /dev/null
+++ b/InvestmentPerformanceWebAPI/Database/ApplicationDbContext.cs
@@ -0,0 +1,103 @@
+using InvestmentPerformanceWebAPI.Models;
+using Microsoft.EntityFrameworkCore;
+using System.Collections.Generic;
+using System.ComponentModel;
+
+namespace InvestmentPerformanceWebAPI.Database
+{
+ public class ApplicationDbContext : DbContext
+ {
+ public DbSet Users { get; set; }
+
+ public DbSet Transactions { get; set; }
+
+ public ApplicationDbContext(DbContextOptions options)
+ : base(options)
+ {
+ }
+
+ ///
+ /// Overriding OnModelCreating to seed initial data into the database.
+ ///
+ ///
+ protected override void OnModelCreating(ModelBuilder modelBuilder)
+ {
+ base.OnModelCreating(modelBuilder);
+
+ // Add users
+ modelBuilder.Entity().HasData(
+ new User
+ {
+ Id = 1, // primary key
+ Username = "testaccount",
+ Email = "testaccount@test.com",
+ }
+ );
+
+ // Add transactions for user with UserId = 1
+ modelBuilder.Entity().HasData(
+
+ new Transaction
+ {
+ Id = 1, // primary key
+ Type = TransactionType.Buy,
+ Name = "International Business Machines",
+ Symbol = "IBM",
+ Quantity = 10,
+ SharePriceAtPurchase = 282.16,
+ CurrentSharePrice = 283.50,
+ TransactionTime = DateTime.UtcNow.AddYears(-1),
+ UserId = 1 // foreign key to User
+ },
+ new Transaction
+ {
+ Id = 2, // primary key
+ Type = TransactionType.Buy,
+ Name = "Fidelity Total Market Index Fund",
+ Symbol = "FSKAX",
+ Quantity = 150,
+ SharePriceAtPurchase = 184.29,
+ CurrentSharePrice = 200.10,
+ TransactionTime = DateTime.UtcNow,
+ UserId = 1 // foreign key to User
+ },
+ new Transaction
+ {
+ Id = 3, // primary key
+ Type = TransactionType.Buy,
+ Name = "Microsoft",
+ Symbol = "MSFT",
+ SharePriceAtPurchase = 282.16,
+ CurrentSharePrice = 300.04,
+ Quantity = 1,
+ TransactionTime = DateTime.UtcNow.AddDays(-4),
+ UserId = 1 // foreign key to User
+ },
+ new Transaction
+ {
+ Id = 4, // primary key
+ Type = TransactionType.Buy,
+ Name = "Vitreous Glass Inc",
+ Symbol = "VCI",
+ Quantity = 5,
+ SharePriceAtPurchase = 6.16,
+ CurrentSharePrice = 8.02,
+ TransactionTime = DateTime.UtcNow.AddYears(-2),
+ UserId = 1 // foreign key to User
+ },
+ new Transaction
+ {
+ Id = 5, // primary key
+ Type = TransactionType.Buy,
+ Name = "Corsair",
+ Symbol = "CRSR",
+ Quantity = 2,
+ SharePriceAtPurchase = 8.72,
+ CurrentSharePrice = 8.02,
+ TransactionTime = DateTime.UtcNow.AddDays(-2),
+ UserId = 1 // foreign key to User
+ }
+ );
+ }
+ }
+}
diff --git a/InvestmentPerformanceWebAPI/InvestmentPerformanceWebAPI.csproj b/InvestmentPerformanceWebAPI/InvestmentPerformanceWebAPI.csproj
new file mode 100644
index 00000000..69a415b6
--- /dev/null
+++ b/InvestmentPerformanceWebAPI/InvestmentPerformanceWebAPI.csproj
@@ -0,0 +1,20 @@
+
+
+
+ net9.0
+ enable
+ enable
+
+
+
+
+
+
+
+ all
+ runtime; build; native; contentfiles; analyzers; buildtransitive
+
+
+
+
+
diff --git a/InvestmentPerformanceWebAPI/InvestmentPerformanceWebAPI.db b/InvestmentPerformanceWebAPI/InvestmentPerformanceWebAPI.db
new file mode 100644
index 00000000..9b6bc217
Binary files /dev/null and b/InvestmentPerformanceWebAPI/InvestmentPerformanceWebAPI.db differ
diff --git a/InvestmentPerformanceWebAPI/InvestmentPerformanceWebAPI.db-shm b/InvestmentPerformanceWebAPI/InvestmentPerformanceWebAPI.db-shm
new file mode 100644
index 00000000..fe9ac284
Binary files /dev/null and b/InvestmentPerformanceWebAPI/InvestmentPerformanceWebAPI.db-shm differ
diff --git a/InvestmentPerformanceWebAPI/InvestmentPerformanceWebAPI.db-wal b/InvestmentPerformanceWebAPI/InvestmentPerformanceWebAPI.db-wal
new file mode 100644
index 00000000..e69de29b
diff --git a/InvestmentPerformanceWebAPI/InvestmentPerformanceWebAPI.sln b/InvestmentPerformanceWebAPI/InvestmentPerformanceWebAPI.sln
new file mode 100644
index 00000000..37c9df3f
--- /dev/null
+++ b/InvestmentPerformanceWebAPI/InvestmentPerformanceWebAPI.sln
@@ -0,0 +1,31 @@
+
+Microsoft Visual Studio Solution File, Format Version 12.00
+# Visual Studio Version 17
+VisualStudioVersion = 17.14.36310.24 d17.14
+MinimumVisualStudioVersion = 10.0.40219.1
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "InvestmentPerformanceWebAPI", "InvestmentPerformanceWebAPI.csproj", "{92148044-E466-4A89-A432-C209C392EDBF}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "InvestmentPerformanceWebAPIUnitTests", "..\InvestmentPerformanceWebAPIUnitTests\InvestmentPerformanceWebAPIUnitTests.csproj", "{32836670-B007-45F2-8872-F6C8FB9E3253}"
+EndProject
+Global
+ GlobalSection(SolutionConfigurationPlatforms) = preSolution
+ Debug|Any CPU = Debug|Any CPU
+ Release|Any CPU = Release|Any CPU
+ EndGlobalSection
+ GlobalSection(ProjectConfigurationPlatforms) = postSolution
+ {92148044-E466-4A89-A432-C209C392EDBF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {92148044-E466-4A89-A432-C209C392EDBF}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {92148044-E466-4A89-A432-C209C392EDBF}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {92148044-E466-4A89-A432-C209C392EDBF}.Release|Any CPU.Build.0 = Release|Any CPU
+ {32836670-B007-45F2-8872-F6C8FB9E3253}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {32836670-B007-45F2-8872-F6C8FB9E3253}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {32836670-B007-45F2-8872-F6C8FB9E3253}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {32836670-B007-45F2-8872-F6C8FB9E3253}.Release|Any CPU.Build.0 = Release|Any CPU
+ EndGlobalSection
+ GlobalSection(SolutionProperties) = preSolution
+ HideSolutionNode = FALSE
+ EndGlobalSection
+ GlobalSection(ExtensibilityGlobals) = postSolution
+ SolutionGuid = {CF7C718A-700A-4E94-AAA5-D59FDFABC2F9}
+ EndGlobalSection
+EndGlobal
diff --git a/InvestmentPerformanceWebAPI/Migrations/20251001171841_InitialCreate.Designer.cs b/InvestmentPerformanceWebAPI/Migrations/20251001171841_InitialCreate.Designer.cs
new file mode 100644
index 00000000..334272b2
--- /dev/null
+++ b/InvestmentPerformanceWebAPI/Migrations/20251001171841_InitialCreate.Designer.cs
@@ -0,0 +1,136 @@
+//
+using System;
+using InvestmentPerformanceWebAPI.Database;
+using Microsoft.EntityFrameworkCore;
+using Microsoft.EntityFrameworkCore.Infrastructure;
+using Microsoft.EntityFrameworkCore.Migrations;
+using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
+
+#nullable disable
+
+namespace InvestmentPerformanceWebAPI.Migrations
+{
+ [DbContext(typeof(ApplicationDbContext))]
+ [Migration("20251001171841_InitialCreate")]
+ partial class InitialCreate
+ {
+ ///
+ protected override void BuildTargetModel(ModelBuilder modelBuilder)
+ {
+#pragma warning disable 612, 618
+ modelBuilder.HasAnnotation("ProductVersion", "9.0.9");
+
+ modelBuilder.Entity("InvestmentPerformanceWebAPI.Models.Transaction", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("INTEGER");
+
+ b.Property("Name")
+ .IsRequired()
+ .HasColumnType("TEXT");
+
+ b.Property("Quantity")
+ .HasColumnType("INTEGER");
+
+ b.Property("Symbol")
+ .IsRequired()
+ .HasColumnType("TEXT");
+
+ b.Property("TransactionTime")
+ .HasColumnType("TEXT");
+
+ b.Property("Type")
+ .HasColumnType("INTEGER");
+
+ b.Property("UserId")
+ .HasColumnType("INTEGER");
+
+ b.HasKey("Id");
+
+ b.HasIndex("UserId");
+
+ b.ToTable("Transactions");
+
+ b.HasData(
+ new
+ {
+ Id = 1,
+ Name = "International Business Machines",
+ Quantity = 10,
+ Symbol = "IBM",
+ TransactionTime = new DateTime(2025, 10, 1, 17, 18, 40, 881, DateTimeKind.Utc).AddTicks(175),
+ Type = 0,
+ UserId = 1
+ },
+ new
+ {
+ Id = 2,
+ Name = "Fidelity Total Market Index Fund",
+ Quantity = 150,
+ Symbol = "FSKAX",
+ TransactionTime = new DateTime(2025, 10, 1, 17, 18, 40, 881, DateTimeKind.Utc).AddTicks(758),
+ Type = 0,
+ UserId = 1
+ },
+ new
+ {
+ Id = 3,
+ Name = "Microsoft",
+ Quantity = 1,
+ Symbol = "MSFT",
+ TransactionTime = new DateTime(2025, 10, 1, 17, 18, 40, 881, DateTimeKind.Utc).AddTicks(761),
+ Type = 0,
+ UserId = 1
+ });
+ });
+
+ modelBuilder.Entity("InvestmentPerformanceWebAPI.Models.User", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("INTEGER");
+
+ b.Property("Email")
+ .IsRequired()
+ .HasColumnType("TEXT");
+
+ b.Property("PasswordHash")
+ .IsRequired()
+ .HasColumnType("TEXT");
+
+ b.Property("Username")
+ .IsRequired()
+ .HasColumnType("TEXT");
+
+ b.HasKey("Id");
+
+ b.ToTable("Users");
+
+ b.HasData(
+ new
+ {
+ Id = 1,
+ Email = "testaccount@test.com",
+ PasswordHash = "AQAAAAIAAYagAAAAEL063horo+HRrTdEbV+L5+8GocegjOlb6XzR0aAzFkKOYs3XFHKtVv2VP9cqng4cwQ==",
+ Username = "testaccount"
+ });
+ });
+
+ modelBuilder.Entity("InvestmentPerformanceWebAPI.Models.Transaction", b =>
+ {
+ b.HasOne("InvestmentPerformanceWebAPI.Models.User", null)
+ .WithMany("Transactions")
+ .HasForeignKey("UserId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired();
+ });
+
+ modelBuilder.Entity("InvestmentPerformanceWebAPI.Models.User", b =>
+ {
+ b.Navigation("Transactions");
+ });
+#pragma warning restore 612, 618
+ }
+ }
+}
diff --git a/InvestmentPerformanceWebAPI/Migrations/20251001171841_InitialCreate.cs b/InvestmentPerformanceWebAPI/Migrations/20251001171841_InitialCreate.cs
new file mode 100644
index 00000000..90689aa2
--- /dev/null
+++ b/InvestmentPerformanceWebAPI/Migrations/20251001171841_InitialCreate.cs
@@ -0,0 +1,86 @@
+using System;
+using Microsoft.EntityFrameworkCore.Migrations;
+
+#nullable disable
+
+#pragma warning disable CA1814 // Prefer jagged arrays over multidimensional
+
+namespace InvestmentPerformanceWebAPI.Migrations
+{
+ ///
+ public partial class InitialCreate : Migration
+ {
+ ///
+ protected override void Up(MigrationBuilder migrationBuilder)
+ {
+ migrationBuilder.CreateTable(
+ name: "Users",
+ columns: table => new
+ {
+ Id = table.Column(type: "INTEGER", nullable: false)
+ .Annotation("Sqlite:Autoincrement", true),
+ Username = table.Column(type: "TEXT", nullable: false),
+ Email = table.Column(type: "TEXT", nullable: false),
+ PasswordHash = table.Column(type: "TEXT", nullable: false)
+ },
+ constraints: table =>
+ {
+ table.PrimaryKey("PK_Users", x => x.Id);
+ });
+
+ migrationBuilder.CreateTable(
+ name: "Transactions",
+ columns: table => new
+ {
+ Id = table.Column(type: "INTEGER", nullable: false)
+ .Annotation("Sqlite:Autoincrement", true),
+ Type = table.Column(type: "INTEGER", nullable: false),
+ Name = table.Column(type: "TEXT", nullable: false),
+ Symbol = table.Column(type: "TEXT", nullable: false),
+ Quantity = table.Column(type: "INTEGER", nullable: false),
+ TransactionTime = table.Column(type: "TEXT", nullable: false),
+ UserId = table.Column(type: "INTEGER", nullable: false)
+ },
+ constraints: table =>
+ {
+ table.PrimaryKey("PK_Transactions", x => x.Id);
+ table.ForeignKey(
+ name: "FK_Transactions_Users_UserId",
+ column: x => x.UserId,
+ principalTable: "Users",
+ principalColumn: "Id",
+ onDelete: ReferentialAction.Cascade);
+ });
+
+ migrationBuilder.InsertData(
+ table: "Users",
+ columns: new[] { "Id", "Email", "PasswordHash", "Username" },
+ values: new object[] { 1, "testaccount@test.com", "AQAAAAIAAYagAAAAEL063horo+HRrTdEbV+L5+8GocegjOlb6XzR0aAzFkKOYs3XFHKtVv2VP9cqng4cwQ==", "testaccount" });
+
+ migrationBuilder.InsertData(
+ table: "Transactions",
+ columns: new[] { "Id", "Name", "Quantity", "Symbol", "TransactionTime", "Type", "UserId" },
+ values: new object[,]
+ {
+ { 1, "International Business Machines", 10, "IBM", new DateTime(2025, 10, 1, 17, 18, 40, 881, DateTimeKind.Utc).AddTicks(175), 0, 1 },
+ { 2, "Fidelity Total Market Index Fund", 150, "FSKAX", new DateTime(2025, 10, 1, 17, 18, 40, 881, DateTimeKind.Utc).AddTicks(758), 0, 1 },
+ { 3, "Microsoft", 1, "MSFT", new DateTime(2025, 10, 1, 17, 18, 40, 881, DateTimeKind.Utc).AddTicks(761), 0, 1 }
+ });
+
+ migrationBuilder.CreateIndex(
+ name: "IX_Transactions_UserId",
+ table: "Transactions",
+ column: "UserId");
+ }
+
+ ///
+ protected override void Down(MigrationBuilder migrationBuilder)
+ {
+ migrationBuilder.DropTable(
+ name: "Transactions");
+
+ migrationBuilder.DropTable(
+ name: "Users");
+ }
+ }
+}
diff --git a/InvestmentPerformanceWebAPI/Migrations/ApplicationDbContextModelSnapshot.cs b/InvestmentPerformanceWebAPI/Migrations/ApplicationDbContextModelSnapshot.cs
new file mode 100644
index 00000000..38ad3a0b
--- /dev/null
+++ b/InvestmentPerformanceWebAPI/Migrations/ApplicationDbContextModelSnapshot.cs
@@ -0,0 +1,133 @@
+//
+using System;
+using InvestmentPerformanceWebAPI.Database;
+using Microsoft.EntityFrameworkCore;
+using Microsoft.EntityFrameworkCore.Infrastructure;
+using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
+
+#nullable disable
+
+namespace InvestmentPerformanceWebAPI.Migrations
+{
+ [DbContext(typeof(ApplicationDbContext))]
+ partial class ApplicationDbContextModelSnapshot : ModelSnapshot
+ {
+ protected override void BuildModel(ModelBuilder modelBuilder)
+ {
+#pragma warning disable 612, 618
+ modelBuilder.HasAnnotation("ProductVersion", "9.0.9");
+
+ modelBuilder.Entity("InvestmentPerformanceWebAPI.Models.Transaction", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("INTEGER");
+
+ b.Property("Name")
+ .IsRequired()
+ .HasColumnType("TEXT");
+
+ b.Property("Quantity")
+ .HasColumnType("INTEGER");
+
+ b.Property("Symbol")
+ .IsRequired()
+ .HasColumnType("TEXT");
+
+ b.Property("TransactionTime")
+ .HasColumnType("TEXT");
+
+ b.Property("Type")
+ .HasColumnType("INTEGER");
+
+ b.Property("UserId")
+ .HasColumnType("INTEGER");
+
+ b.HasKey("Id");
+
+ b.HasIndex("UserId");
+
+ b.ToTable("Transactions");
+
+ b.HasData(
+ new
+ {
+ Id = 1,
+ Name = "International Business Machines",
+ Quantity = 10,
+ Symbol = "IBM",
+ TransactionTime = new DateTime(2025, 10, 1, 17, 18, 40, 881, DateTimeKind.Utc).AddTicks(175),
+ Type = 0,
+ UserId = 1
+ },
+ new
+ {
+ Id = 2,
+ Name = "Fidelity Total Market Index Fund",
+ Quantity = 150,
+ Symbol = "FSKAX",
+ TransactionTime = new DateTime(2025, 10, 1, 17, 18, 40, 881, DateTimeKind.Utc).AddTicks(758),
+ Type = 0,
+ UserId = 1
+ },
+ new
+ {
+ Id = 3,
+ Name = "Microsoft",
+ Quantity = 1,
+ Symbol = "MSFT",
+ TransactionTime = new DateTime(2025, 10, 1, 17, 18, 40, 881, DateTimeKind.Utc).AddTicks(761),
+ Type = 0,
+ UserId = 1
+ });
+ });
+
+ modelBuilder.Entity("InvestmentPerformanceWebAPI.Models.User", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("INTEGER");
+
+ b.Property("Email")
+ .IsRequired()
+ .HasColumnType("TEXT");
+
+ b.Property("PasswordHash")
+ .IsRequired()
+ .HasColumnType("TEXT");
+
+ b.Property("Username")
+ .IsRequired()
+ .HasColumnType("TEXT");
+
+ b.HasKey("Id");
+
+ b.ToTable("Users");
+
+ b.HasData(
+ new
+ {
+ Id = 1,
+ Email = "testaccount@test.com",
+ PasswordHash = "AQAAAAIAAYagAAAAEL063horo+HRrTdEbV+L5+8GocegjOlb6XzR0aAzFkKOYs3XFHKtVv2VP9cqng4cwQ==",
+ Username = "testaccount"
+ });
+ });
+
+ modelBuilder.Entity("InvestmentPerformanceWebAPI.Models.Transaction", b =>
+ {
+ b.HasOne("InvestmentPerformanceWebAPI.Models.User", null)
+ .WithMany("Transactions")
+ .HasForeignKey("UserId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired();
+ });
+
+ modelBuilder.Entity("InvestmentPerformanceWebAPI.Models.User", b =>
+ {
+ b.Navigation("Transactions");
+ });
+#pragma warning restore 612, 618
+ }
+ }
+}
diff --git a/InvestmentPerformanceWebAPI/Program.cs b/InvestmentPerformanceWebAPI/Program.cs
new file mode 100644
index 00000000..9e216093
--- /dev/null
+++ b/InvestmentPerformanceWebAPI/Program.cs
@@ -0,0 +1,50 @@
+using InvestmentPerformanceWebAPI.Database;
+using Microsoft.EntityFrameworkCore;
+
+var builder = WebApplication.CreateBuilder(args);
+
+// Add services to the container.
+
+builder.Services.AddControllers();
+// Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle
+builder.Services.AddEndpointsApiExplorer();
+builder.Services.AddSwaggerGen();
+
+// using sqllite
+builder.Services.AddDbContext(options =>
+ options.UseSqlite(builder.Configuration.GetConnectionString("SqliteConnection") ??
+ throw new InvalidOperationException("Connection string 'SqliteConnection' not found."))
+);
+
+var app = builder.Build();
+
+// Ensure database is created and up to date
+using (var scope = app.Services.CreateScope())
+{
+ var context = scope.ServiceProvider.GetRequiredService();
+ context.Database.EnsureCreated();
+ // Alternative: Use migrations instead of EnsureCreated()
+ // context.Database.Migrate();
+}
+
+if (app.Environment.IsDevelopment())
+{
+ app.UseDeveloperExceptionPage();
+}
+
+app.UseSwagger();
+app.UseSwaggerUI(c =>
+{
+ c.SwaggerEndpoint("/swagger/v1/swagger.json", "InvestmentPerformanceWebAPI v1");
+ c.RoutePrefix = "";
+});
+
+app.UseHttpsRedirection();
+
+app.UseRouting();
+
+app.UseAuthorization();
+
+app.MapControllers();
+
+app.Run();
\ No newline at end of file
diff --git a/InvestmentPerformanceWebAPI/Properties/launchSettings.json b/InvestmentPerformanceWebAPI/Properties/launchSettings.json
new file mode 100644
index 00000000..40ef0b2b
--- /dev/null
+++ b/InvestmentPerformanceWebAPI/Properties/launchSettings.json
@@ -0,0 +1,50 @@
+{
+ "$schema": "https://json.schemastore.org/launchsettings.json",
+ "profiles": {
+ "http": {
+ "commandName": "Project",
+ "dotnetRunMessages": true,
+ "launchBrowser": true,
+ "launchUrl": "",
+ "environmentVariables": {
+ "ASPNETCORE_ENVIRONMENT": "Debug"
+ },
+ "applicationUrl": "http://localhost:5053"
+ },
+ "https": {
+ "commandName": "Project",
+ "dotnetRunMessages": true,
+ "launchBrowser": true,
+ "launchUrl": "",
+ "applicationUrl": "https://localhost:7005;http://localhost:5053",
+ "environmentVariables": {
+ "ASPNETCORE_ENVIRONMENT": "Development"
+ }
+ },
+ "IIS Express": {
+ "commandName": "IISExpress",
+ "launchBrowser": true,
+ "launchUrl": "",
+ "environmentVariables": {
+ "ASPNETCORE_ENVIRONMENT": "Development"
+ }
+ },
+ "ProductionEnvironment": {
+ "commandName": "Project",
+ "launchBrowser": true,
+ "launchUrl": "",
+ "environmentVariables": {
+ "ASPNETCORE_ENVIRONMENT": "Production"
+ },
+ "applicationUrl": "http://localhost"
+ }
+ },
+ "iisSettings": {
+ "windowsAuthentication": false,
+ "anonymousAuthentication": true,
+ "iisExpress": {
+ "applicationUrl": "http://localhost:63776",
+ "sslPort": 44343
+ }
+ }
+}
diff --git a/InvestmentPerformanceWebAPI/appsettings.Development.json b/InvestmentPerformanceWebAPI/appsettings.Development.json
new file mode 100644
index 00000000..7963f01b
--- /dev/null
+++ b/InvestmentPerformanceWebAPI/appsettings.Development.json
@@ -0,0 +1,11 @@
+{
+ "Logging": {
+ "LogLevel": {
+ "Default": "Information",
+ "Microsoft.AspNetCore": "Warning"
+ }
+ },
+ "ConnectionStrings": {
+ "SqliteConnection": "Data Source=InvestmentPerformanceWebAPI.db"
+ }
+}
diff --git a/InvestmentPerformanceWebAPI/appsettings.json b/InvestmentPerformanceWebAPI/appsettings.json
new file mode 100644
index 00000000..a796390b
--- /dev/null
+++ b/InvestmentPerformanceWebAPI/appsettings.json
@@ -0,0 +1,12 @@
+{
+ "Logging": {
+ "LogLevel": {
+ "Default": "Information",
+ "Microsoft.AspNetCore": "Warning"
+ }
+ },
+ "ConnectionStrings": {
+ "SqliteConnection": "Data Source=InvestmentPerformanceWebAPI.db"
+ },
+ "AllowedHosts": "*"
+}
diff --git a/InvestmentPerformanceWebAPIUnitTests/InvestmentPerformanceWebAPIUnitTests.csproj b/InvestmentPerformanceWebAPIUnitTests/InvestmentPerformanceWebAPIUnitTests.csproj
new file mode 100644
index 00000000..9d1c0d46
--- /dev/null
+++ b/InvestmentPerformanceWebAPIUnitTests/InvestmentPerformanceWebAPIUnitTests.csproj
@@ -0,0 +1,27 @@
+
+
+
+ net9.0
+ latest
+ enable
+ enable
+ false
+ true
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/InvestmentPerformanceWebAPIUnitTests/MSTestSettings.cs b/InvestmentPerformanceWebAPIUnitTests/MSTestSettings.cs
new file mode 100644
index 00000000..aaf278c8
--- /dev/null
+++ b/InvestmentPerformanceWebAPIUnitTests/MSTestSettings.cs
@@ -0,0 +1 @@
+[assembly: Parallelize(Scope = ExecutionScope.MethodLevel)]
diff --git a/InvestmentPerformanceWebAPIUnitTests/UserControllerTests.cs b/InvestmentPerformanceWebAPIUnitTests/UserControllerTests.cs
new file mode 100644
index 00000000..ba7fb647
--- /dev/null
+++ b/InvestmentPerformanceWebAPIUnitTests/UserControllerTests.cs
@@ -0,0 +1,229 @@
+using InvestmentPerformanceWebAPI.Controllers;
+using InvestmentPerformanceWebAPI.Database;
+using InvestmentPerformanceWebAPI.DataTransferObjects;
+using InvestmentPerformanceWebAPI.Models;
+using Microsoft.AspNetCore.Mvc;
+using Microsoft.EntityFrameworkCore;
+using Microsoft.VisualStudio.TestTools.UnitTesting;
+
+namespace InvestmentPerformanceWebAPIUnitTests
+{
+ [TestClass]
+ public class UserControllerTests
+ {
+ private ApplicationDbContext _context = null!;
+ private UserController _controller = null!;
+
+ [TestInitialize]
+ public void Setup()
+ {
+ var options = new DbContextOptionsBuilder()
+ .UseInMemoryDatabase(databaseName: Guid.NewGuid().ToString())
+ .Options;
+
+ _context = new ApplicationDbContext(options);
+ _controller = new UserController(_context);
+ SeedTestData();
+ }
+
+ [TestCleanup]
+ public void Cleanup()
+ {
+ _context?.Dispose();
+ }
+
+ private void SeedTestData()
+ {
+ var user = new User
+ {
+ Id = 1,
+ Username = "testuser",
+ Email = "test@example.com",
+ Transactions = new List
+ {
+ new Transaction
+ {
+ Id = 1,
+ Name = "Apple Inc.",
+ Symbol = "AAPL",
+ Quantity = 10,
+ SharePriceAtPurchase = 150.00,
+ CurrentSharePrice = 175.00,
+ TransactionTime = DateTime.UtcNow.AddDays(-30),
+ UserId = 1,
+ Type = TransactionType.Buy
+ },
+ new Transaction
+ {
+ Id = 2,
+ Name = "Microsoft Corp.",
+ Symbol = "MSFT",
+ Quantity = 5,
+ SharePriceAtPurchase = 300.00,
+ CurrentSharePrice = 320.00,
+ TransactionTime = DateTime.UtcNow.AddDays(-15),
+ UserId = 1,
+ Type = TransactionType.Buy
+ }
+ }
+ };
+
+ _context.Users.Add(user);
+ _context.SaveChanges();
+ }
+
+ [TestMethod]
+ public void GetUser_WithValidId_ReturnsOkResultWithUserDTO()
+ {
+ int userId = 1;
+ var result = _controller.GetUser(userId);
+
+ Assert.IsInstanceOfType(result, typeof(OkObjectResult));
+ var okResult = (OkObjectResult)result;
+
+ Assert.IsInstanceOfType(okResult.Value, typeof(IQueryable));
+ var userQuery = (IQueryable)okResult.Value!;
+ var userDto = userQuery.FirstOrDefault();
+
+ Assert.IsNotNull(userDto);
+ Assert.AreEqual(userId, userDto.Id);
+ Assert.AreEqual("testuser", userDto.Username);
+ Assert.AreEqual(2, userDto.Transactions.Count);
+ }
+
+ [TestMethod]
+ public void GetUser_WithValidId_ReturnsCorrectTransactionDetails()
+ {
+ int userId = 1;
+ var result = _controller.GetUser(userId);
+
+ var okResult = (OkObjectResult)result;
+ var userQuery = (IQueryable)okResult.Value!;
+ var userDto = userQuery.FirstOrDefault();
+
+ Assert.IsNotNull(userDto);
+ var appleTransaction = userDto.Transactions.FirstOrDefault(t => t.Name == "Apple Inc.");
+ Assert.IsNotNull(appleTransaction);
+ Assert.AreEqual(1, appleTransaction.Id);
+ Assert.AreEqual(10, appleTransaction.Shares);
+ Assert.AreEqual(150.00, appleTransaction.CostBasisPerShare);
+ Assert.AreEqual(175.00, appleTransaction.CurrentPrice);
+ Assert.AreEqual(1750.00, appleTransaction.CurrentValue);
+ Assert.AreEqual(250.00, appleTransaction.TotalGain);
+
+ var microsoftTransaction = userDto.Transactions.FirstOrDefault(t => t.Name == "Microsoft Corp.");
+ Assert.IsNotNull(microsoftTransaction);
+ Assert.AreEqual(2, microsoftTransaction.Id);
+ Assert.AreEqual(5, microsoftTransaction.Shares);
+ Assert.AreEqual(300.00, microsoftTransaction.CostBasisPerShare);
+ Assert.AreEqual(320.00, microsoftTransaction.CurrentPrice);
+ Assert.AreEqual(1600.00, microsoftTransaction.CurrentValue);
+ Assert.AreEqual(100.00, microsoftTransaction.TotalGain);
+ }
+
+ [TestMethod]
+ public void GetUser_WithInvalidId_ReturnsOkWithEmptyQueryable()
+ {
+ int invalidUserId = 999;
+ var result = _controller.GetUser(invalidUserId);
+
+ Assert.IsInstanceOfType(result, typeof(OkObjectResult));
+ var okResult = (OkObjectResult)result;
+ var userQuery = (IQueryable)okResult.Value!;
+ var userDto = userQuery.FirstOrDefault();
+
+ Assert.IsNull(userDto);
+ }
+
+ [TestMethod]
+ public void GetUser_NullContext_ThrowsNullReferenceException()
+ {
+ var controller = new UserController(null);
+
+ Assert.ThrowsException(() => controller.GetUser(1));
+ }
+
+ [TestMethod]
+ public void GetUser_VerifyTransactionCalculations_AreCorrect()
+ {
+ var testUser = new User
+ {
+ Id = 3,
+ Username = "calculationtest",
+ Email = "calc@test.com",
+ Transactions = new List
+ {
+ new Transaction
+ {
+ Id = 10,
+ Name = "Test Stock",
+ Symbol = "TEST",
+ Quantity = 100,
+ SharePriceAtPurchase = 50.0,
+ CurrentSharePrice = 60.0,
+ TransactionTime = DateTime.UtcNow,
+ UserId = 3,
+ Type = TransactionType.Buy
+ }
+ }
+ };
+
+ _context.Users.Add(testUser);
+ _context.SaveChanges();
+
+ var result = _controller.GetUser(3);
+
+ var okResult = (OkObjectResult)result;
+ var userQuery = (IQueryable)okResult.Value!;
+ var userDto = userQuery.FirstOrDefault();
+ Assert.IsNotNull(userDto);
+ var transaction = userDto.Transactions.First();
+
+ Assert.AreEqual(6000.0, transaction.CurrentValue);
+ Assert.AreEqual(1000.0, transaction.TotalGain);
+ Assert.AreEqual(50.0, transaction.CostBasisPerShare);
+ Assert.AreEqual(60.0, transaction.CurrentPrice);
+ Assert.AreEqual(100, transaction.Shares);
+ }
+
+ [TestMethod]
+ public void GetUser_LossTransaction_CalculatesNegativeGain()
+ {
+ var userWithLoss = new User
+ {
+ Id = 4,
+ Username = "lossuser",
+ Email = "loss@example.com",
+ Transactions = new List
+ {
+ new Transaction
+ {
+ Id = 20,
+ Name = "Loss Stock",
+ Symbol = "LOSS",
+ Quantity = 50,
+ SharePriceAtPurchase = 100.0,
+ CurrentSharePrice = 80.0,
+ TransactionTime = DateTime.UtcNow,
+ UserId = 4,
+ Type = TransactionType.Buy
+ }
+ }
+ };
+
+ _context.Users.Add(userWithLoss);
+ _context.SaveChanges();
+
+ var result = _controller.GetUser(4);
+
+ var okResult = (OkObjectResult)result;
+ var userQuery = (IQueryable)okResult.Value!;
+ var userDto = userQuery.FirstOrDefault();
+ Assert.IsNotNull(userDto);
+ var transaction = userDto.Transactions.First();
+
+ Assert.AreEqual(4000.0, transaction.CurrentValue);
+ Assert.AreEqual(-1000.0, transaction.TotalGain);
+ }
+ }
+}
\ No newline at end of file
diff --git a/ReadMe.md b/ReadMe.md
index ad2afefb..ed967d1c 100644
--- a/ReadMe.md
+++ b/ReadMe.md
@@ -1,3 +1,21 @@
+# Executing the code
+
+Open "InvestmentPerformanceWebAPI.sln"
+
+You can build and run the code in visual studio or by the .exe in the bin/debug / bin/release folder.
+
+It will open up the browser and go to the swagger rest api. You can then hit the end points there in order to test the uesr stories.
+
+The url for swagger that opens up should be https://localhost:7005/index.html
+
+# Assumptions Made
+
+- Populated some data with a simple sql lite database using C# entity framework code first. I know in a real world example there would be data already there, or technically we could have used a third party API to pull stock information, but felt that was to easy to do.
+
+- There is currently one user (id: 1)
+
+- Five transactions for that user. (id: 1-5)
+
# Coding Exercise
> This repository holds coding exercises for candidates going through the hiring process.