diff --git a/.gitignore b/.gitignore index 6769715..2f13dfa 100644 --- a/.gitignore +++ b/.gitignore @@ -282,6 +282,9 @@ _pkginfo.txt # but keep track of directories ending in .cache !*.[Cc]ache/ +# Secrets (connection strings, API keys) +**/Secrets.json + # Others ClientBin/ ~$* diff --git a/CommBank-Server/CommBank.csproj b/CommBank-Server/CommBank.csproj index 983cc88..b57f2c4 100644 --- a/CommBank-Server/CommBank.csproj +++ b/CommBank-Server/CommBank.csproj @@ -1,7 +1,7 @@ - net6.0 + net9.0 enable enable CommBank_Server @@ -13,7 +13,7 @@ - + diff --git a/CommBank-Server/Controllers/GoalController.cs b/CommBank-Server/Controllers/GoalController.cs index 98271a5..d014799 100644 --- a/CommBank-Server/Controllers/GoalController.cs +++ b/CommBank-Server/Controllers/GoalController.cs @@ -1,4 +1,4 @@ -using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Mvc; using CommBank.Services; using CommBank.Models; @@ -79,6 +79,7 @@ public async Task Update(string id, Goal updatedGoal) } updatedGoal.Id = goal.Id; + updatedGoal.UserId = goal.UserId ?? updatedGoal.UserId; await _goalsService.UpdateAsync(id, updatedGoal); diff --git a/CommBank-Server/Models/Goal.cs b/CommBank-Server/Models/Goal.cs index 77ff1ad..ed46440 100644 --- a/CommBank-Server/Models/Goal.cs +++ b/CommBank-Server/Models/Goal.cs @@ -1,4 +1,4 @@ -using MongoDB.Bson; +using MongoDB.Bson; using MongoDB.Bson.Serialization.Attributes; namespace CommBank.Models; @@ -11,6 +11,8 @@ public class Goal public string? Name { get; set; } + public string? Icon { get; set; } + public UInt64 TargetAmount { get; set; } = 0; public DateTime TargetDate { get; set; } diff --git a/CommBank-Server/Program.cs b/CommBank-Server/Program.cs index a88e560..8e2d53f 100644 --- a/CommBank-Server/Program.cs +++ b/CommBank-Server/Program.cs @@ -1,17 +1,38 @@ -using CommBank.Models; +using System.Text.Json.Serialization; +using CommBank.Models; using CommBank.Services; using MongoDB.Driver; +// Prefer TLS 1.2 for MongoDB Atlas (helps avoid "TLS alert: InternalError" on Windows) +System.Net.ServicePointManager.SecurityProtocol = System.Net.SecurityProtocolType.Tls12; + var builder = WebApplication.CreateBuilder(args); -builder.Services.AddControllers(); +builder.Services.AddControllers() + .AddJsonOptions(options => + { + options.JsonSerializerOptions.Converters.Add(new JsonStringEnumConverter(System.Text.Json.JsonNamingPolicy.CamelCase, allowIntegerValues: false)); + }); builder.Services.AddEndpointsApiExplorer(); builder.Services.AddSwaggerGen(); -builder.Configuration.SetBasePath(Directory.GetCurrentDirectory()).AddJsonFile("Secrets.json"); - -var mongoClient = new MongoClient(builder.Configuration.GetConnectionString("CommBank")); +builder.Configuration.SetBasePath(Directory.GetCurrentDirectory()) + .AddJsonFile("Secrets.json", optional: true); + +var connectionString = builder.Configuration.GetConnectionString("CommBank"); +var mongoClientSettings = MongoClientSettings.FromConnectionString(connectionString); +mongoClientSettings.ServerSelectionTimeout = TimeSpan.FromSeconds(60); +// On Windows, OCSP revocation check can cause TLS "InternalError" handshake failure with Atlas +var ssl = mongoClientSettings.SslSettings ?? new SslSettings(); +ssl.CheckCertificateRevocation = false; +mongoClientSettings.SslSettings = ssl; +// Development-only: relax TLS cert validation if Windows still fails (remove in production) +if (builder.Environment.IsDevelopment()) +{ + mongoClientSettings.AllowInsecureTls = true; +} +var mongoClient = new MongoClient(mongoClientSettings); var mongoDatabase = mongoClient.GetDatabase("CommBank"); IAccountsService accountsService = new AccountsService(mongoDatabase); diff --git a/CommBank-Server/Secrets.json b/CommBank-Server/Secrets.json deleted file mode 100644 index 0e5bf94..0000000 --- a/CommBank-Server/Secrets.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "ConnectionStrings": { - "CommBank": "{CONNECTION_STRING}" - } -} \ No newline at end of file diff --git a/CommBank-Server/Secrets.json.example b/CommBank-Server/Secrets.json.example new file mode 100644 index 0000000..c10b90b --- /dev/null +++ b/CommBank-Server/Secrets.json.example @@ -0,0 +1,5 @@ +{ + "ConnectionStrings": { + "CommBank": "mongodb+srv://:@.mongodb.net/?appName=YourApp" + } +} diff --git a/CommBank-Server/Services/AccountService.cs b/CommBank-Server/Services/AccountService.cs index 52d1cb9..28f9615 100644 --- a/CommBank-Server/Services/AccountService.cs +++ b/CommBank-Server/Services/AccountService.cs @@ -1,6 +1,7 @@ using Microsoft.Extensions.Options; using CommBank.Models; using MongoDB.Driver; +using MongoDB.Bson; namespace CommBank.Services; @@ -19,8 +20,15 @@ public async Task> GetAsync() => public async Task GetAsync(string id) => await _accountsCollection.Find(x => x.Id == id).FirstOrDefaultAsync(); - public async Task CreateAsync(Account newAccount) => + public async Task CreateAsync(Account newAccount) + { + if (string.IsNullOrWhiteSpace(newAccount.Id) || !ObjectId.TryParse(newAccount.Id, out _)) + { + newAccount.Id = ObjectId.GenerateNewId().ToString(); + } + await _accountsCollection.InsertOneAsync(newAccount); + } public async Task UpdateAsync(string id, Account updatedAccount) => await _accountsCollection.ReplaceOneAsync(x => x.Id == id, updatedAccount); diff --git a/CommBank-Server/appsettings.Development.json b/CommBank-Server/appsettings.Development.json index ce16a2e..5509279 100644 --- a/CommBank-Server/appsettings.Development.json +++ b/CommBank-Server/appsettings.Development.json @@ -1,9 +1,17 @@ -{ +{ "Logging": { "LogLevel": { "Default": "Information", "Microsoft.AspNetCore": "Warning" } + }, + "ConnectionStrings": { + "CommBank": "mongodb+srv://YOUR_DB_USERNAME:YOUR_DB_PASSWORD@aorta.boe9w7l.mongodb.net/?appName=Aorta" + }, + "MongoDbSettings": { + "ConnectionString": "mongodb+srv://YOUR_DB_USERNAME:YOUR_DB_PASSWORD@aorta.boe9w7l.mongodb.net/?appName=Aorta", + "DatabaseName": "commbank" } } + diff --git a/CommBank.Tests/CommBank.Tests.csproj b/CommBank.Tests/CommBank.Tests.csproj index 4d9413f..95c6765 100644 --- a/CommBank.Tests/CommBank.Tests.csproj +++ b/CommBank.Tests/CommBank.Tests.csproj @@ -1,7 +1,7 @@ - net6.0 + net9.0 enable enable diff --git a/CommBank.Tests/GoalControllerTests.cs b/CommBank.Tests/GoalControllerTests.cs index 8380181..c5df1e4 100644 --- a/CommBank.Tests/GoalControllerTests.cs +++ b/CommBank.Tests/GoalControllerTests.cs @@ -1,4 +1,4 @@ -using CommBank.Controllers; +using CommBank.Controllers; using CommBank.Services; using CommBank.Models; using CommBank.Tests.Fake; @@ -66,9 +66,27 @@ public async void Get() public async void GetForUser() { // Arrange - + var goals = collections.GetGoals(); + var users = collections.GetUsers(); + var userId = users[0].Id!; + goals[0].UserId = userId; + goals[1].UserId = userId; + goals[2].UserId = userId; + IGoalsService goalsService = new FakeGoalsService(goals, goals[0]); + IUsersService usersService = new FakeUsersService(users, users[0]); + GoalController controller = new(goalsService, usersService); + // Act - + var httpContext = new Microsoft.AspNetCore.Http.DefaultHttpContext(); + controller.ControllerContext.HttpContext = httpContext; + var result = await controller.GetForUser(userId); + // Assert + Assert.NotNull(result); + foreach (Goal goal in result!) + { + Assert.IsAssignableFrom(goal); + Assert.Equal(userId, goal.UserId); + } } } \ No newline at end of file diff --git a/POSTMAN_TESTING.md b/POSTMAN_TESTING.md new file mode 100644 index 0000000..4feff1b --- /dev/null +++ b/POSTMAN_TESTING.md @@ -0,0 +1,181 @@ +# Testing the CommBank API with Postman + +## 1. Start the server + +From the repo root: + +```bash +cd CommBank-Server +dotnet run +``` + +Or run from Visual Studio / Rider. The API will listen on **http://localhost:5203** (or http://localhost:11366 — check the console for the port). + +Use this as your **Base URL** in Postman: `http://localhost:5203` (replace with your actual port if different). + +--- + +## 2. Set up Postman + +- **Headers:** For POST/PUT, set `Content-Type: application/json` (Postman usually does this when you pick "raw" + "JSON"). +- **Body:** For POST/PUT, choose **Body** → **raw** → **JSON**. + +--- + +## 3. Test the main endpoints + +### Health check (optional) + +- **GET** `http://localhost:5203/swagger/index.html` + Opens Swagger UI in the browser so you can see all endpoints. + +--- + +### Accounts + +| Method | URL | Body (for POST/PUT) | +|--------|-----|----------------------| +| GET all | `http://localhost:5203/api/Account` | — | +| GET one | `http://localhost:5203/api/Account/{id}` | — | +| POST | `http://localhost:5203/api/Account` | See below | +| PUT | `http://localhost:5203/api/Account/{id}` | Same shape as POST | +| DELETE | `http://localhost:5203/api/Account/{id}` | — | + +**Example POST body (create account):** +```json +{ + "name": "My Savings", + "number": 12345678, + "balance": 0, + "accountType": "goalSaver" +} +``` +Use `"accountType": "goalSaver"` or `"netBankSaver"`. For GET one / PUT / DELETE, `{id}` must be a 24-character MongoDB ObjectId (e.g. from a previous GET or POST response). + +--- + +### Users + +| Method | URL | Body | +|--------|-----|------| +| GET all | `http://localhost:5203/api/User` | — | +| GET one | `http://localhost:5203/api/User/{id}` | — | +| POST | `http://localhost:5203/api/User` | See below | +| PUT | `http://localhost:5203/api/User/{id}` | Same shape | +| DELETE | `http://localhost:5203/api/User/{id}` | — | + +**Example POST body (create user — password is hashed by the server):** +```json +{ + "name": "Test User", + "email": "test@example.com", + "password": "secret123", + "accountIds": [], + "goalIds": [], + "transactionIds": [] +} +``` + +--- + +### Auth (login) + +- **POST** `http://localhost:5203/api/Auth/Login` + +**Body:** +```json +{ + "email": "test@example.com", + "password": "secret123" +} +``` +- Success: **204 No Content**. +- Wrong email/password: **404 Not Found**. + +--- + +### Goals + +| Method | URL | Body | +|--------|-----|------| +| GET all | `http://localhost:5203/api/Goal` | — | +| GET one | `http://localhost:5203/api/Goal/{id}` | — | +| GET by user | `http://localhost:5203/api/Goal/User/{userId}` | — | +| POST | `http://localhost:5203/api/Goal` | See below | +| PUT | `http://localhost:5203/api/Goal/{id}` | Same shape | +| DELETE | `http://localhost:5203/api/Goal/{id}` | — | + +**Example POST body:** +```json +{ + "name": "Holiday Fund", + "icon": "plane", + "targetAmount": 5000, + "targetDate": "2026-12-31T00:00:00Z", + "balance": 0, + "userId": "PUT_A_USER_OBJECT_ID_HERE" +} +``` +`icon` is optional. Get a real `userId` from GET `/api/User`. + +--- + +### Transactions + +| Method | URL | Body | +|--------|-----|------| +| GET all | `http://localhost:5203/api/Transaction` | — | +| GET one | `http://localhost:5203/api/Transaction/{id}` | — | +| GET by user | `http://localhost:5203/api/Transaction/User/{userId}` | — | +| POST | `http://localhost:5203/api/Transaction` | See below | +| PUT | `http://localhost:5203/api/Transaction/{id}` | Same shape | +| DELETE | `http://localhost:5203/api/Transaction/{id}` | — | + +**Example POST body:** +```json +{ + "transactionType": "credit", + "amount": 100.50, + "dateTime": "2026-03-05T12:00:00Z", + "description": "Salary", + "userId": "PUT_A_USER_OBJECT_ID_HERE" +} +``` +Use `transactionType`: `"credit"`, `"debit"`, or `"transfer"`. `goalId` and `tagIds` are optional. + +--- + +### Tags + +| Method | URL | Body | +|--------|-----|------| +| GET all | `http://localhost:5203/api/Tag` | — | +| GET one | `http://localhost:5203/api/Tag/{id}` | — | +| POST | `http://localhost:5203/api/Tag` | `{ "name": "Groceries" }` | +| PUT | `http://localhost:5203/api/Tag/{id}` | `{ "name": "Food" }` | +| DELETE | `http://localhost:5203/api/Tag/{id}` | — | + +--- + +## 4. Suggested test order + +1. **GET** `/api/Account` and `/api/User` — see if the server and DB connection work (empty arrays `[]` is OK). +2. **POST** `/api/User` — create a user; copy the returned `id`. +3. **POST** `/api/Auth/Login` — login with that user’s email/password (expect 204). +4. **POST** `/api/Account` — create an account (use `"accountType": "goalSaver"` or `"netBankSaver"`). +5. **POST** `/api/Goal` — create a goal with the user’s `userId`; optionally include `"icon": "plane"`. +6. **GET** `/api/Goal` or `/api/Goal/User/{userId}` — confirm the goal (and optional `icon`) is returned. +7. **POST** `/api/Transaction` and **POST** `/api/Tag` — then GET to verify. + +--- + +## 5. What to expect + +- **GET (list):** `200` with a JSON array (possibly empty `[]`). +- **GET (one):** `200` with one object, or `404` if id not found. +- **POST:** `201 Created` with the created resource (including generated `id`) in the response. +- **PUT:** `204 No Content` on success, `404` if id not found. +- **DELETE:** `204 No Content` on success, `404` if id not found. +- **Login:** `204` on success, `404` if email/password wrong. + +If you get **400**, check the request body (valid JSON, correct field names like `accountType`, `transactionType`, and allowed enum values). If you get **500**, check the server console and that MongoDB is reachable (e.g. connection string and Atlas network access). diff --git a/README.md b/README.md new file mode 100644 index 0000000..9febb0d --- /dev/null +++ b/README.md @@ -0,0 +1,18 @@ +Completed all tasks including MongoDB integration and Goal Model modification. + +## Running the server + +1. **Set the MongoDB connection string** (required for `dotnet run` to work): + - Open `CommBank-Server/appsettings.Development.json`. + - Replace `YOUR_CONNECTION_STRING_HERE` with your real MongoDB connection string in both `ConnectionStrings:CommBank` and `MongoDbSettings:ConnectionString`. + - Or create `CommBank-Server/Secrets.json` (same folder as `Program.cs`) with: + ```json + { + "ConnectionStrings": { + "CommBank": "mongodb+srv://user:password@cluster.xxxxx.mongodb.net/?retryWrites=true&w=majority" + } + } + ``` +2. From `CommBank-Server` run: `dotnet run`. + +**Getting a free connection string:** Sign up at [MongoDB Atlas](https://www.mongodb.com/cloud/atlas), create a free M0 cluster, add a database user, add network access (e.g. `0.0.0.0/0` for testing), then use the “Connect” → “Drivers” connection string. diff --git a/expected_response.md b/expected_response.md new file mode 100644 index 0000000..260ab01 --- /dev/null +++ b/expected_response.md @@ -0,0 +1,18 @@ +# Expected Response + +Example response from **GET /api/Goal** after completing MongoDB integration and adding the optional `icon` field to the Goal model (real data from this project): + +```json +{ + "id": "69a9cb57f72212af18e0c289", + "name": "Buy a Car", + "icon": "car-emoji", + "targetAmount": 20000, + "targetDate": "2026-12-31T00:00:00Z", + "balance": 500, + "created": "2026-03-05T21:28:39.0119093+03:00", + "transactionIds": null, + "tagIds": null, + "userId": null +} +```