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

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/dotnet.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ name: .NET Build and Test

on:
push:
branches: [ "main" ]
branches: [ "main", "refactor/clean-architecture" ]
pull_request:
branches: [ "main" ]

Expand Down
13 changes: 12 additions & 1 deletion Finance.Api.Tests/CustomWebApplicationFactory.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,8 @@
using Finance.Infrastructure.Data;
using Finance.Application.Interfaces.Repositories;
using Finance.Contracts.Interfaces.Services;
using Finance.Infrastructure.Data;
using Finance.Infrastructure.Repositories;
using Finance.Infrastructure.Services;
using Microsoft.AspNetCore.Mvc.Testing;
using Microsoft.AspNetCore.TestHost;
using Microsoft.EntityFrameworkCore;
Expand Down Expand Up @@ -54,6 +58,13 @@ protected override void ConfigureWebHost(IWebHostBuilder builder)

builder.UseSetting("ConnectionStrings:WriteDatabase", writeCs);
builder.UseSetting("ConnectionStrings:ReadDatabase", readCs);

services.AddScoped<ICategoryRepository, CategoryRepository>();
services.AddScoped<ITransactionRepository, TransactionRepository>();
services.AddScoped<IUserRepository, UserRepository>();

services.AddScoped<ITokenService, TokenService>();
services.AddScoped<ICacheService, CacheService>();
});
}
}
38 changes: 30 additions & 8 deletions Finance.Api/Controllers/AuthController.cs
Original file line number Diff line number Diff line change
@@ -1,21 +1,29 @@
using Finance.API.Extensions;
using Finance.Contracts.Interfaces.Services;
using Finance.Application.Features.Auth.GetProfile;
using Finance.Application.Features.Auth.Login;
using Finance.Application.Features.Auth.Register;
using Finance.Application.Features.Auth.UpdateProfile;
using Finance.Contracts.Requests.Auth;
using MediatR;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;

namespace Finance.Api.Controllers;

[ApiController]
[Route("v1/auth")]
public class AuthController(IUserService userService) : ControllerBase
public class AuthController(IMediator mediator) : ControllerBase
{
private readonly IUserService _userService = userService;

[HttpPost("login")]
public async Task<IActionResult> Login([FromBody] LoginRequest request)
{
var response = await _userService.LoginAsync(request);
var command = new LoginUserCommand
{
Email = request.Email,
Password = request.Password
};

var response = await mediator.Send(command);

if (!response.IsSuccess)
return Unauthorized(response.Message);
Expand All @@ -26,7 +34,14 @@ public async Task<IActionResult> Login([FromBody] LoginRequest request)
[HttpPost("register")]
public async Task<IActionResult> Register([FromBody] RegisterRequest request)
{
var response = await _userService.RegisterAsync(request);
var command = new RegisterUserCommand
{
Name = request.Name,
Email = request.Email,
Password = request.Password
};

var response = await mediator.Send(command);

if (!response.IsSuccess)
return BadRequest(response.Message);
Expand All @@ -38,15 +53,22 @@ public async Task<IActionResult> Register([FromBody] RegisterRequest request)
[HttpGet("profile")]
public async Task<IActionResult> GetProfileAsync()
{
var response = await _userService.GetProfileAsync();
var command = new GetProfileCommand();
var response = await mediator.Send(command);
return this.FromResponse(response);
}

[Authorize]
[HttpPut("profile")]
public async Task<IActionResult> UpdateProfileAsync([FromBody] UpdateUserProfileRequest request)
{
var response = await _userService.UpdateProfileAsync(request);
var command = new UpdateProfileCommand
{
Name = request.Name,
Email = request.Email
};

var response = await mediator.Send(command);
return this.FromResponse(response);
Comment on lines 61 to 72
Copy link

Copilot AI Apr 7, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

UpdateProfileAsync receives UpdateUserProfileRequest with both Name and Email, and UpdateProfileCommand also has an Email property, but the controller only populates Name. This effectively ignores the request email and will pass an empty Email if the handler ever starts using it. Map request.Email into the command (or remove Email from the request/command if email updates are intentionally unsupported).

Copilot uses AI. Check for mistakes.
}
}
41 changes: 28 additions & 13 deletions Finance.Api/Controllers/CategoriesController.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
using Finance.Application.Extensions;
using Finance.Contracts.Interfaces.Services;
using Finance.Application.Features.Categories.Create;
using Finance.Application.Features.Categories.Delete;
using Finance.Application.Features.Categories.GetAll;
using Finance.Application.Features.Categories.GetById;
using Finance.Application.Features.Categories.Update;
using Finance.Contracts.Requests.Categories;
using MediatR;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;

Expand All @@ -9,14 +14,19 @@ namespace Finance.Api.Controllers;
[Authorize]
[ApiController]
[Route("v1/categories")]
public class CategoriesController(ICategoryService service) : ControllerBase
public class CategoriesController(IMediator mediator) : ControllerBase
{
[HttpPost]
public async Task<IActionResult> CreateAsync([FromBody] CreateCategoryRequest request)
{
request.UserId = User.GetUserId();
var command = new CreateCategoryCommand
{
Title = request.Title,
Description = request.Description,
UserId = User.GetUserId()
};

var response = await service.CreateAsync(request);
var response = await mediator.Send(command);

return response.IsSuccess
? Created($"v1/categories/{response.Data?.Id}", response.Data)
Expand All @@ -26,10 +36,15 @@ public async Task<IActionResult> CreateAsync([FromBody] CreateCategoryRequest re
[HttpPut("{id:long}")]
public async Task<IActionResult> UpdateAsync([FromRoute] long id, [FromBody] UpdateCategoryRequest request)
{
request.Id = id;
request.UserId = User.GetUserId();
var command = new UpdateCategoryCommand
{
Id = id,
Title = request.Title,
Description = request.Description,
UserId = User.GetUserId()
};

var response = await service.UpdateAsync(request);
var response = await mediator.Send(command);

return response.IsSuccess
? Ok(response.Data)
Expand All @@ -39,13 +54,13 @@ public async Task<IActionResult> UpdateAsync([FromRoute] long id, [FromBody] Upd
[HttpDelete("{id:long}")]
public async Task<IActionResult> DeleteAsync([FromRoute] long id)
{
var request = new DeleteCategoryRequest
var command = new DeleteCategoryCommand
{
Id = id,
UserId = User.GetUserId()
};

var response = await service.DeleteAsync(request);
var response = await mediator.Send(command);

return response.IsSuccess
? Ok(response.Data)
Expand All @@ -55,13 +70,13 @@ public async Task<IActionResult> DeleteAsync([FromRoute] long id)
[HttpGet("{id:long}")]
public async Task<IActionResult> GetByIdAsync([FromRoute] long id)
{
var request = new GetCategoryByIdRequest
var command = new GetCategoryByIdCommand
{
Id = id,
UserId = User.GetUserId()
};

var response = await service.GetByIdAsync(request);
var response = await mediator.Send(command);

return response.IsSuccess
? Ok(response.Data)
Expand All @@ -71,14 +86,14 @@ public async Task<IActionResult> GetByIdAsync([FromRoute] long id)
[HttpGet]
public async Task<IActionResult> GetAllAsync([FromQuery] int pageNumber = 1, [FromQuery] int pageSize = 25)
{
var request = new GetAllCategoriesRequest
var command = new GetAllCategoriesCommand
{
UserId = User.GetUserId(),
PageNumber = pageNumber,
PageSize = pageSize
};

var response = await service.GetAllAsync(request);
var response = await mediator.Send(command);

return response.IsSuccess
? Ok(response.Data)
Expand Down
52 changes: 37 additions & 15 deletions Finance.Api/Controllers/TransactionsController.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,12 @@
using Finance.Application.Extensions;
using Finance.Contracts.Interfaces.Services;
using Finance.Application.Features.Transactions.Create;
using Finance.Application.Features.Transactions.Delete;
using Finance.Application.Features.Transactions.GetById;
using Finance.Application.Features.Transactions.GetByPeriod;
using Finance.Application.Features.Transactions.GetReport;
using Finance.Application.Features.Transactions.Update;
using Finance.Contracts.Requests.Transactions;
using MediatR;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;

Expand All @@ -9,14 +15,22 @@ namespace Finance.Api.Controllers;
[Authorize]
[ApiController]
[Route("v1/transactions")]
public class TransactionsController(ITransactionService service) : ControllerBase
public class TransactionsController(IMediator mediator) : ControllerBase
{
[HttpPost]
public async Task<IActionResult> CreateAsync([FromBody] CreateTransactionRequest request)
{
request.UserId = User.GetUserId();
var command = new CreateTransactionCommand
{
Title = request.Title,
Amount = request.Amount,
Type = request.Type,
CategoryId = request.CategoryId,
PaidOrReceivedAt = request.PaidOrReceivedAt,
UserId = User.GetUserId()
};

var response = await service.CreateAsync(request);
var response = await mediator.Send(command);

return response.IsSuccess
? Created($"v1/transactions/{response.Data?.Id}", response.Data)
Expand All @@ -26,10 +40,18 @@ public async Task<IActionResult> CreateAsync([FromBody] CreateTransactionRequest
[HttpPut("{id:long}")]
public async Task<IActionResult> UpdateAsync([FromRoute] long id, [FromBody] UpdateTransactionRequest request)
{
request.Id = id;
request.UserId = User.GetUserId();
var command = new UpdateTransactionCommand
{
Id = id,
Title = request.Title,
Amount = request.Amount,
Type = request.Type,
CategoryId = request.CategoryId,
PaidOrReceivedAt = request.PaidOrReceivedAt,
UserId = User.GetUserId()
};

var response = await service.UpdateAsync(request);
var response = await mediator.Send(command);

return response.IsSuccess
? Ok(response.Data)
Expand All @@ -39,13 +61,13 @@ public async Task<IActionResult> UpdateAsync([FromRoute] long id, [FromBody] Upd
[HttpDelete("{id:long}")]
public async Task<IActionResult> DeleteAsync([FromRoute] long id)
{
var request = new DeleteTransactionRequest
var command = new DeleteTransactionCommand
{
Id = id,
UserId = User.GetUserId()
};

var response = await service.DeleteAsync(request);
var response = await mediator.Send(command);

return response.IsSuccess
? Ok(response.Data)
Expand All @@ -55,13 +77,13 @@ public async Task<IActionResult> DeleteAsync([FromRoute] long id)
[HttpGet("{id:long}")]
public async Task<IActionResult> GetByIdAsync([FromRoute] long id)
{
var request = new GetTransactionByIdRequest
var command = new GetByIdTransactionCommand
{
Id = id,
UserId = User.GetUserId()
};

var response = await service.GetByIdAsync(request);
var response = await mediator.Send(command);

return response.IsSuccess
? Ok(response.Data)
Expand All @@ -75,7 +97,7 @@ public async Task<IActionResult> GetByPeriodAsync(
[FromQuery] int pageNumber = 1,
[FromQuery] int pageSize = 25)
{
var request = new GetTransactionsByPeriodRequest
var command = new GetByPeriodTransactionCommand
{
UserId = User.GetUserId(),
StartDate = startDate,
Expand All @@ -84,7 +106,7 @@ public async Task<IActionResult> GetByPeriodAsync(
PageSize = pageSize
};

var response = await service.GetByPeriodAsync(request);
var response = await mediator.Send(command);

return response.IsSuccess
? Ok(response.Data)
Expand All @@ -94,14 +116,14 @@ public async Task<IActionResult> GetByPeriodAsync(
[HttpGet("report")]
public async Task<IActionResult> GetReportAsync([FromQuery] DateTime? startDate = null, [FromQuery] DateTime? endDate = null)
{
var request = new GetTransactionReportRequest
var command = new GetReportTransactionCommand
{
UserId = User.GetUserId(),
StartDate = startDate,
EndDate = endDate
};

var response = await service.GetReportAsync(request);
var response = await mediator.Send(command);

return response.IsSuccess
? Ok(response.Data)
Expand Down
8 changes: 1 addition & 7 deletions Finance.Api/Extensions/BuilderExtension.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,7 @@
using Finance.Application.Features.Transactions.Create;
using Finance.Application.Features.Transactions.GetByPeriod;
using Finance.Application.Features.Transactions.Update;
using Finance.Application.Services;
using Finance.Contracts.Interfaces.Repositories;
using Finance.Application.Interfaces.Repositories;
using Finance.Contracts.Interfaces.Services;
using Finance.Infrastructure.Data;
using Finance.Infrastructure.Outbox;
Expand Down Expand Up @@ -72,7 +71,6 @@ public static WebApplicationBuilder AddApiInfrastructure(this WebApplicationBuil
builder.AddCache();
builder.AddCors();
builder.AddDocumentation();
builder.AddServices();
builder.AddMediatR();

builder.Services.AddHttpContextAccessor();
Expand Down Expand Up @@ -183,10 +181,6 @@ public static void AddServices(this WebApplicationBuilder builder)
builder.Services.AddScoped<ITransactionRepository, TransactionRepository>();
builder.Services.AddScoped<IUserRepository, UserRepository>();

builder.Services.AddScoped<ICategoryService, CategoryService>();
builder.Services.AddScoped<ITransactionService, TransactionService>();
builder.Services.AddScoped<IUserService, UserService>();

builder.Services.AddScoped<ITokenService, TokenService>();
builder.Services.AddScoped<ICacheService, CacheService>();

Expand Down
Loading
Loading