diff --git a/.gitignore b/.gitignore
index 40b878d..cb22fd5 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1 +1,7 @@
-node_modules/
\ No newline at end of file
+node_modules/
+
+# Ignore .NET build outputs
+bin/
+obj/
+**/bin/
+**/obj/
\ No newline at end of file
diff --git a/code/.gitignore b/code/.gitignore
new file mode 100644
index 0000000..cbbd0b5
--- /dev/null
+++ b/code/.gitignore
@@ -0,0 +1,2 @@
+bin/
+obj/
\ No newline at end of file
diff --git a/code/CommBank.Tests/CommBank.Tests.csproj b/code/CommBank.Tests/CommBank.Tests.csproj
new file mode 100644
index 0000000..8673505
--- /dev/null
+++ b/code/CommBank.Tests/CommBank.Tests.csproj
@@ -0,0 +1,23 @@
+
+
+
+ net10.0
+ false
+
+
+
+
+
+
+ all
+ runtime; build; native; contentfiles; analyzers; buildtransitive
+
+
+
+
+
+
+
+
+
+
diff --git a/code/CommBank.Tests/Fakes/FakeGoalsService.cs b/code/CommBank.Tests/Fakes/FakeGoalsService.cs
new file mode 100644
index 0000000..b037e17
--- /dev/null
+++ b/code/CommBank.Tests/Fakes/FakeGoalsService.cs
@@ -0,0 +1,45 @@
+using System.Collections.Generic;
+using System.Linq;
+using System.Threading.Tasks;
+using CommBank.Models;
+using CommBank.Services;
+
+namespace CommBank.Tests.Fakes;
+
+public class FakeGoalsService : IGoalsService
+{
+ private readonly List _goals;
+
+ public FakeGoalsService(List goals)
+ {
+ _goals = goals;
+ }
+
+ public Task> GetAsync() =>
+ Task.FromResult(_goals);
+
+ public Task GetAsync(string id) =>
+ Task.FromResult(_goals.FirstOrDefault(g => g.Id == id));
+
+ public Task CreateAsync(Goal newGoal)
+ {
+ _goals.Add(newGoal);
+ return Task.CompletedTask;
+ }
+
+ public Task UpdateAsync(string id, Goal updatedGoal)
+ {
+ var idx = _goals.FindIndex(g => g.Id == id);
+ if (idx >= 0) _goals[idx] = updatedGoal;
+ return Task.CompletedTask;
+ }
+
+ public Task RemoveAsync(string id)
+ {
+ _goals.RemoveAll(g => g.Id == id);
+ return Task.CompletedTask;
+ }
+
+ public Task> GetForUserAsync(string userId) =>
+ Task.FromResult(_goals.Where(g => g.UserId == userId).ToList());
+}
diff --git a/code/CommBank.Tests/GoalControllerTests.cs b/code/CommBank.Tests/GoalControllerTests.cs
new file mode 100644
index 0000000..258bc6f
--- /dev/null
+++ b/code/CommBank.Tests/GoalControllerTests.cs
@@ -0,0 +1,39 @@
+using System.Collections.Generic;
+using System.Threading.Tasks;
+using Xunit;
+using CommBank.Controllers;
+using CommBank.Models;
+using CommBank.Tests.Fakes;
+
+
+public class GoalControllerTests
+{
+ [Fact]
+ public async Task GetForUser_ReturnsOnlyGoalsForThatUser()
+ {
+ // Arrange
+ var userId = "62a3f5e0102e921da1253d33";
+
+ var goals = new List
+ {
+ new Goal { Id = "62a3f5e0102e921da1253d32", UserId = userId, Name = "G1" },
+ new Goal { Id = "62a3f5e0102e921da1253d34", UserId = userId, Name = "G2" },
+ new Goal { Id = "62a3f5e0102e921da1253d99", UserId = "62a3f5e0102e921da1253d00", Name = "Other user" },
+ };
+
+ var goalsService = new FakeGoalsService(goals);
+ var controller = new GoalController(goalsService);
+
+ // Act
+ var result = await controller.GetForUser(userId);
+
+ // Assert
+ Assert.NotNull(result);
+
+ foreach (var goal in result)
+ {
+ Assert.IsAssignableFrom(goal);
+ Assert.Equal(userId, goal.UserId);
+ }
+ }
+}
diff --git a/code/CommBank/CommBank.csproj b/code/CommBank/CommBank.csproj
new file mode 100644
index 0000000..a9a8042
--- /dev/null
+++ b/code/CommBank/CommBank.csproj
@@ -0,0 +1,15 @@
+
+
+
+ net10.0
+ enable
+ enable
+
+
+
+
+
+
+
+
+
diff --git a/code/CommBank/CommBank.http b/code/CommBank/CommBank.http
new file mode 100644
index 0000000..2224628
--- /dev/null
+++ b/code/CommBank/CommBank.http
@@ -0,0 +1,6 @@
+@CommBank_HostAddress = http://localhost:5117
+
+GET {{CommBank_HostAddress}}/weatherforecast/
+Accept: application/json
+
+###
diff --git a/code/CommBank/Controllers/GoalController.cs b/code/CommBank/Controllers/GoalController.cs
new file mode 100644
index 0000000..745995b
--- /dev/null
+++ b/code/CommBank/Controllers/GoalController.cs
@@ -0,0 +1,75 @@
+using Microsoft.AspNetCore.Mvc;
+using CommBank.Models;
+using CommBank.Services;
+
+namespace CommBank.Controllers;
+
+[ApiController]
+[Route("api/[controller]")]
+public class GoalController : ControllerBase
+{
+ private readonly IGoalsService _goalsService;
+
+ public GoalController(IGoalsService goalsService) =>
+ _goalsService = goalsService;
+
+ [HttpGet]
+ public async Task> Get() =>
+ await _goalsService.GetAsync();
+
+ [HttpGet("{id:length(24)}")]
+ public async Task> Get(string id)
+ {
+ var goal = await _goalsService.GetAsync(id);
+ if (goal is null)
+ {
+ return NotFound();
+ }
+ return goal;
+
+ }
+
+ [HttpGet("user/{userId:length(24)}")]
+ public async Task> GetForUser(string userId) =>
+ await _goalsService.GetForUserAsync(userId);
+
+
+ [HttpPost]
+ public async Task Post(Goal newGoal)
+ {
+ await _goalsService.CreateAsync(newGoal);
+ return CreatedAtAction(nameof(Get), new{ id = newGoal.Id}, newGoal);
+
+ }
+
+ [HttpPut("{id:length(24)}")]
+ public async Task Update(string id, Goal updatedGoal)
+ {
+ var goal = await _goalsService.GetAsync(id);
+ if (goal is null)
+ {
+ return NotFound();
+ }
+
+ updatedGoal.Id = goal.Id;
+
+ await _goalsService.UpdateAsync(id, updatedGoal);
+ return NoContent();
+ }
+
+ [HttpDelete("{id:length(24)}")]
+ public async Task Delete(string id)
+ {
+ var goal = await _goalsService.GetAsync(id);
+
+ if(goal is null)
+ {
+ return NotFound();
+ }
+
+ await _goalsService.RemoveAsync(id);
+ return NoContent();
+ }
+
+
+}
\ No newline at end of file
diff --git a/code/CommBank/Models/Goal.cs b/code/CommBank/Models/Goal.cs
new file mode 100644
index 0000000..c8b1850
--- /dev/null
+++ b/code/CommBank/Models/Goal.cs
@@ -0,0 +1,35 @@
+using MongoDB.Bson;
+using MongoDB.Bson.Serialization.Attributes;
+
+namespace CommBank.Models;
+
+public class Goal
+{
+ [BsonId]
+ [BsonRepresentation(BsonType.ObjectId)]
+ public string? Id { get; set; }
+
+ public string? Name { get; set; }
+
+ public UInt64 TargetAmount { get; set; } = 0;
+
+ public DateTime TargetDate { get; set; }
+
+ public double Balance { get; set; } = 0.00;
+
+ public DateTime Created { get; set; } = DateTime.Now;
+
+ [BsonRepresentation(BsonType.ObjectId)]
+ public string? AccountId { get; set; }
+
+ [BsonRepresentation(BsonType.ObjectId)]
+ public List? TransactionIds { get; set; }
+
+ [BsonRepresentation(BsonType.ObjectId)]
+ public List? TagIds { get; set; }
+
+ [BsonRepresentation(BsonType.ObjectId)]
+ public string? UserId { get; set; }
+
+ public string? Icon { get; set; }
+}
\ No newline at end of file
diff --git a/code/CommBank/Program.cs b/code/CommBank/Program.cs
new file mode 100644
index 0000000..e1105fa
--- /dev/null
+++ b/code/CommBank/Program.cs
@@ -0,0 +1,37 @@
+using CommBank.Services;
+using MongoDB.Driver;
+
+var builder = WebApplication.CreateBuilder(args);
+
+builder.Configuration.SetBasePath(Directory.GetCurrentDirectory())
+ .AddJsonFile("Secrets.json");
+
+// mongo + db instance
+var MongoClient = new MongoClient(builder.Configuration.GetConnectionString("Commbank"));
+var mongoDatabase = MongoClient.GetDatabase("data");
+
+// test connection to db
+try
+{
+ await mongoDatabase.RunCommandAsync((Command)"{ping:1}");
+ Console.WriteLine("✅ MongoDB connection OK (ping succeeded)");
+}
+catch (Exception ex)
+{
+ Console.WriteLine("❌ MongoDB connection failed: " + ex.Message);
+}
+
+
+// register dependencies for DI
+IGoalsService goalsService = new GoalsService(mongoDatabase);
+builder.Services.AddSingleton(goalsService);
+
+// enable controoller to handle routes
+builder.Services.AddControllers();
+
+var app = builder.Build();
+
+// map routes
+app.MapControllers();
+
+app.Run();
\ No newline at end of file
diff --git a/code/CommBank/Properties/launchSettings.json b/code/CommBank/Properties/launchSettings.json
new file mode 100644
index 0000000..7864a12
--- /dev/null
+++ b/code/CommBank/Properties/launchSettings.json
@@ -0,0 +1,41 @@
+{
+ "$schema": "http://json.schemastore.org/launchsettings.json",
+ "iisSettings": {
+ "windowsAuthentication": false,
+ "anonymousAuthentication": true,
+ "iisExpress": {
+ "applicationUrl": "http://localhost:13994",
+ "sslPort": 44316
+ }
+ },
+ "profiles": {
+ "http": {
+ "commandName": "Project",
+ "dotnetRunMessages": true,
+ "launchBrowser": true,
+ "launchUrl": "swagger",
+ "applicationUrl": "http://localhost:5117",
+ "environmentVariables": {
+ "ASPNETCORE_ENVIRONMENT": "Development"
+ }
+ },
+ "https": {
+ "commandName": "Project",
+ "dotnetRunMessages": true,
+ "launchBrowser": true,
+ "launchUrl": "swagger",
+ "applicationUrl": "https://localhost:7298;http://localhost:5117",
+ "environmentVariables": {
+ "ASPNETCORE_ENVIRONMENT": "Development"
+ }
+ },
+ "IIS Express": {
+ "commandName": "IISExpress",
+ "launchBrowser": true,
+ "launchUrl": "swagger",
+ "environmentVariables": {
+ "ASPNETCORE_ENVIRONMENT": "Development"
+ }
+ }
+ }
+}
diff --git a/code/CommBank/Secrets.json b/code/CommBank/Secrets.json
new file mode 100644
index 0000000..1fafaa5
--- /dev/null
+++ b/code/CommBank/Secrets.json
@@ -0,0 +1,5 @@
+{
+ "ConnectionStrings": {
+ "CommBank": "mongodb+srv://user1:fYvb7tH3TQaKuPtd@cbafencer.y1186pi.mongodb.net/?appName=cbaFencer"
+ }
+}
\ No newline at end of file
diff --git a/code/CommBank/Services/GoalsService.cs b/code/CommBank/Services/GoalsService.cs
new file mode 100644
index 0000000..365f977
--- /dev/null
+++ b/code/CommBank/Services/GoalsService.cs
@@ -0,0 +1,34 @@
+using Microsoft.Extensions.Options;
+using CommBank.Models;
+using MongoDB.Driver;
+
+namespace CommBank.Services;
+
+public class GoalsService : IGoalsService
+{
+ private readonly IMongoCollection _goalsCollection;
+
+ public GoalsService(IMongoDatabase mongoDatabase)
+ {
+ _goalsCollection = mongoDatabase.GetCollection("Goals");
+
+ }
+ public async Task> GetAsync() =>
+ await _goalsCollection.Find(_ => true).ToListAsync();
+
+ public async Task GetAsync(string id) =>
+ await _goalsCollection.Find(x => x.Id == id).FirstOrDefaultAsync();
+
+ public async Task CreateAsync(Goal newGoal) =>
+ await _goalsCollection.InsertOneAsync(newGoal);
+
+ public async Task UpdateAsync(string id, Goal updatedGoal) =>
+ await _goalsCollection.ReplaceOneAsync(x => x.Id == id, updatedGoal);
+
+ public async Task RemoveAsync(string id) =>
+ await _goalsCollection.DeleteOneAsync(x => x.Id == id);
+
+ public async Task> GetForUserAsync(string userId) =>
+ await _goalsCollection.Find(g => g.UserId == userId).ToListAsync();
+
+}
diff --git a/code/CommBank/Services/IGoalsService.cs b/code/CommBank/Services/IGoalsService.cs
new file mode 100644
index 0000000..843e2ef
--- /dev/null
+++ b/code/CommBank/Services/IGoalsService.cs
@@ -0,0 +1,15 @@
+using CommBank.Models;
+
+namespace CommBank.Services
+{
+ public interface IGoalsService
+ {
+ Task CreateAsync(Goal newGoal);
+ Task> GetAsync();
+ Task GetAsync(string id);
+ Task RemoveAsync(string id);
+ Task UpdateAsync(string id, Goal updatedGoal);
+
+ Task> GetForUserAsync(string userId);
+ }
+}
\ No newline at end of file
diff --git a/code/CommBank/ans.cs b/code/CommBank/ans.cs
new file mode 100644
index 0000000..a6c0eb6
--- /dev/null
+++ b/code/CommBank/ans.cs
@@ -0,0 +1,13 @@
+// {
+// "id": "62a3f587102e921da1253d32",
+// "name": "House Down Payment",
+// "targetAmount": 100000,
+// "targetDate": "2025-01-08T05:00:00Z",
+// "balance": 73501.82,
+// "created": "2022-06-11T01:53:10.857Z",
+// "accountId": null,
+// "transactionIds": null,
+// "tagIds": null,
+// "userId": "62a29c15f4605c4c9fa7f306",
+// "icon": "🤺"
+// }
\ No newline at end of file
diff --git a/code/CommBank/appsettings.Development.json b/code/CommBank/appsettings.Development.json
new file mode 100644
index 0000000..0c208ae
--- /dev/null
+++ b/code/CommBank/appsettings.Development.json
@@ -0,0 +1,8 @@
+{
+ "Logging": {
+ "LogLevel": {
+ "Default": "Information",
+ "Microsoft.AspNetCore": "Warning"
+ }
+ }
+}
diff --git a/code/CommBank/appsettings.json b/code/CommBank/appsettings.json
new file mode 100644
index 0000000..10f68b8
--- /dev/null
+++ b/code/CommBank/appsettings.json
@@ -0,0 +1,9 @@
+{
+ "Logging": {
+ "LogLevel": {
+ "Default": "Information",
+ "Microsoft.AspNetCore": "Warning"
+ }
+ },
+ "AllowedHosts": "*"
+}
diff --git a/code/Task1.zip b/code/Task1.zip
new file mode 100644
index 0000000..f17a2bd
Binary files /dev/null and b/code/Task1.zip differ
diff --git a/code/task1_ans.txt b/code/task1_ans.txt
new file mode 100644
index 0000000..5838d06
--- /dev/null
+++ b/code/task1_ans.txt
@@ -0,0 +1,13 @@
+{
+ "id": "62a3f587102e921da1253d32",
+ "name": "House Down Payment",
+ "targetAmount": 100000,
+ "targetDate": "2025-01-08T05:00:00Z",
+ "balance": 73501.82,
+ "created": "2022-06-11T01:53:10.857Z",
+ "accountId": null,
+ "transactionIds": null,
+ "tagIds": null,
+ "userId": "62a29c15f4605c4c9fa7f306",
+ "icon": "🤺"
+}
\ No newline at end of file