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
5 changes: 5 additions & 0 deletions .idea/.idea.Fin-Backend/.idea/dataSources.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

7 changes: 7 additions & 0 deletions .idea/.idea.Fin-Backend/.idea/data_source_mapping.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

7 changes: 4 additions & 3 deletions Fin-Backend.sln.DotSettings.user
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
<wpf:ResourceDictionary xml:space="preserve" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:s="clr-namespace:System;assembly=mscorlib" xmlns:ss="urn:shemas-jetbrains-com:settings-storage-xaml" xmlns:wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003ABadHttpRequestException_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002Econfig_003FJetBrains_003FRider2025_002E2_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003F58fd8e539d784853aa0a483642931ebf4c000_003F16_003Fbb4f7d05_003FBadHttpRequestException_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AModelBuilder_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002Econfig_003FJetBrains_003FRider2025_002E2_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003Fd83f823935e343bfaf4ef4c6263b8a12291438_003F33_003Fc1982b24_003FModelBuilder_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AParameterExpression_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002Econfig_003FJetBrains_003FRider2025_002E2_003Fresharper_002Dhost_003FSourcesCache_003F6182caf029c8666b96a8b3bae244ef29b988c59b58e9b3439b794a4f8195bec_003FParameterExpression_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AType_002ECoreCLR_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002Econfig_003FJetBrains_003FRider2025_002E2_003Fresharper_002Dhost_003FSourcesCache_003F7e333a9f3297ba553cccfd3b7c3f1f96125b23d09f883e4d6e66d531559a4c_003FType_002ECoreCLR_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
<s:String x:Key="/Default/Environment/UnitTesting/UnitTestSessionStore/Sessions/=85a9ffa2_002D6d16_002D4824_002Da26f_002De2d6d0602eb2/@EntryIndexedValue">&lt;SessionState ContinuousTestingMode="0" IsActive="True" Name="All tests from Solution" xmlns="urn:schemas-jetbrains-com:jetbrains-ut-session"&gt;
&lt;Solution /&gt;
&lt;/SessionState&gt;</s:String>
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AValidationResult_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002Econfig_003FJetBrains_003FRider2025_002E2_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003Fa6e670ad021647bd9cd5d3c28cc553172c800_003F99_003F09c94557_003FValidationResult_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>

</wpf:ResourceDictionary>
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
using Fin.Application.FinancialInstitutions;
using Fin.Application.FinancialInstitutions.Dtos;
using Fin.Domain.FinancialInstitutions.Dtos;
using Fin.Domain.Global.Classes;
using Fin.Infrastructure.Authentications.Constants;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;

Expand All @@ -24,27 +26,31 @@ public async Task<ActionResult<FinancialInstitutionOutput>> Get([FromRoute] Guid
}

[HttpPost]
[Authorize(Roles = AuthenticationRoles.Admin)]
public async Task<ActionResult<FinancialInstitutionOutput>> Create([FromBody] FinancialInstitutionInput input)
{
var institution = await service.Create(input, autoSave: true);
return institution != null ? Created($"financial-institutions/{institution.Id}", institution) : UnprocessableEntity();
}

[HttpPut("{id:guid}")]
[Authorize(Roles = AuthenticationRoles.Admin)]
public async Task<ActionResult> Update([FromRoute] Guid id, [FromBody] FinancialInstitutionInput input)
{
var updated = await service.Update(id, input, autoSave: true);
return updated ? Ok() : NotFound();
}

[HttpDelete("{id:guid}")]
[Authorize(Roles = AuthenticationRoles.Admin)]
public async Task<ActionResult> Delete([FromRoute] Guid id)
{
var deleted = await service.Delete(id, autoSave: true);
return deleted ? Ok() : NotFound();
}

[HttpPatch("{id:guid}/toggle-inactive")]
[Authorize(Roles = AuthenticationRoles.Admin)]
public async Task<ActionResult> ToggleInactive([FromRoute] Guid id)
{
var toggled = await service.ToggleInactive(id, autoSave: true);
Expand Down
14 changes: 13 additions & 1 deletion Fin.Api/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,26 @@
using Fin.Infrastructure.Extensions;
using Fin.Infrastructure.Seeders.Extensions;
using Hangfire;
using NSwag;

var builder = WebApplication.CreateBuilder(args);

var frontEndUrl = builder.Configuration.GetSection(AppConstants.FrontUrlConfigKey).Get<string>();

builder.Services
.AddInfrastructure(builder.Configuration)
.AddOpenApiDocument()
.AddOpenApiDocument(config =>
{
config.Title = "FinApp API";
config.Version = "v1";

config.AddSecurity("Bearer", [], new OpenApiSecurityScheme
{
Type = OpenApiSecuritySchemeType.Http,
Scheme = "bearer",
BearerFormat = "JWT",
});
})
.AddCors(options =>
{
options.AddPolicy("AllowAngularLocalhost",
Expand Down
33 changes: 20 additions & 13 deletions Fin.Api/TitleCategories/TitleCategoryController.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
using Fin.Application.TitleCategories;
using Fin.Application.TitleCategories.Dtos;
using Fin.Application.TitleCategories.Enums;
using Fin.Domain.Global.Classes;
using Fin.Domain.TitleCategories.Dtos;
using Microsoft.AspNetCore.Authorization;
Expand All @@ -9,46 +10,52 @@ namespace Fin.Api.TitleCategories;

[Route("title-categories")]
[Authorize]
public class TitleCategoryController(ITitleCategoryService service): ControllerBase
public class TitleCategoryController(ITitleCategoryService service) : ControllerBase
{
[HttpGet]
public async Task<PagedOutput<TitleCategoryOutput>> GetList([FromQuery] TitleCategoryGetListInput input)
{
return await service.GetList(input);
}

[HttpGet("{id:guid}")]
public async Task<ActionResult<TitleCategoryOutput>> Get([FromRoute] Guid id)
{
var category = await service.Get(id);
return category != null ? Ok(category) : NotFound();
return category != null ? Ok(category) : NotFound();
}

[HttpPost]
public async Task<ActionResult<TitleCategoryOutput>> Create([FromBody] TitleCategoryInput input)
{
var category = await service.Create(input, autoSave: true);
return category != null ? Created($"categories/{category.Id}", category) : UnprocessableEntity();
var validationResult = await service.Create(input, autoSave: true);
return validationResult.Success
? Created($"categories/{validationResult.Data?.Id}", validationResult.Data)
: UnprocessableEntity(validationResult);
}

[HttpPut("{id:guid}")]
public async Task<ActionResult> Update([FromRoute] Guid id, [FromBody] TitleCategoryInput input)
{
var updated = await service.Update(id, input, autoSave: true);
return updated ? Ok() : UnprocessableEntity();
var validationResult = await service.Update(id, input, autoSave: true);
return validationResult.Success
? Ok()
: validationResult.ErrorCode == TitleCategoryCreateOrUpdateErrorCode.TitleCategoryNotFound
? NotFound(validationResult)
: UnprocessableEntity(validationResult);
}

[HttpPut("toggle-inactivated/{id:guid}")]
public async Task<ActionResult> ToggleInactivated([FromRoute] Guid id)
{
var updated = await service.ToggleInactive(id, autoSave: true);
return updated ? Ok() : UnprocessableEntity();
return updated ? Ok() : NotFound();
}

[HttpDelete("{id:guid}")]
public async Task<ActionResult> Delete([FromRoute] Guid id)
{
var deleted = await service.Delete(id, autoSave: true);
return deleted ? Ok() : UnprocessableEntity();
return deleted ? Ok() : NotFound();
}
}
63 changes: 63 additions & 0 deletions Fin.Api/Wallets/WalletController.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
using Fin.Application.Wallets.Dtos;
using Fin.Application.Wallets.Enums;
using Fin.Application.Wallets.Services;
using Fin.Domain.Global.Classes;
using Fin.Domain.Wallets.Dtos;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;

namespace Fin.Api.Wallets;

[Route("wallets")]
[Authorize]
public class WalletController(IWalletService service) : ControllerBase
{
[HttpGet]
public async Task<PagedOutput<WalletOutput>> GetList([FromQuery] WalletGetListInput input)
{
return await service.GetList(input);
}

[HttpGet("{id:guid}")]
public async Task<ActionResult<WalletOutput>> Get([FromRoute] Guid id)
{
var category = await service.Get(id);
return category != null ? Ok(category) : NotFound();
}

[HttpPost]
public async Task<ActionResult<WalletOutput>> Create([FromBody] WalletInput input)
{
var validationResult = await service.Create(input, autoSave: true);
return validationResult.Success
? Created($"categories/{validationResult.Data?.Id}", validationResult.Data)
: UnprocessableEntity(validationResult);
}

[HttpPut("{id:guid}")]
public async Task<ActionResult> Update([FromRoute] Guid id, [FromBody] WalletInput input)
{
var validationResult = await service.Update(id, input, autoSave: true);
return validationResult.Success ? Ok() :
validationResult.ErrorCode == WalletCreateOrUpdateErrorCode.WalletNotFound ? NotFound(validationResult) :
UnprocessableEntity(validationResult);
}

[HttpPut("toggle-inactivated/{id:guid}")]
public async Task<ActionResult> ToggleInactivated([FromRoute] Guid id)
{
var validationResult = await service.ToggleInactive(id, autoSave: true);
return validationResult.Success ? Ok() :
validationResult.ErrorCode == WalletToggleInactiveErrorCode.WalletNotFound ? NotFound(validationResult) :
UnprocessableEntity(validationResult);
}

[HttpDelete("{id:guid}")]
public async Task<ActionResult> Delete([FromRoute] Guid id)
{
var validationResult = await service.Delete(id, autoSave: true);
return validationResult.Success ? Ok() :
validationResult.ErrorCode == WalletDeleteErrorCode.WalletNotFound ? NotFound(validationResult) :
UnprocessableEntity(validationResult);
}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
using Fin.Domain.FinancialInstitutions.Enums;
using Fin.Domain.Global.Classes;

namespace Fin.Application.FinancialInstitutions.Dtos;

public class FinancialInstitutionGetListInput : PagedFilteredAndSortedInput
{
public bool? Inactive { get; set; }
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
using Fin.Application.FinancialInstitutions.Dtos;
using Fin.Domain.FinancialInstitutions.Dtos;
using Fin.Domain.FinancialInstitutions.Entities;
using Fin.Domain.Global.Classes;
Expand All @@ -12,12 +13,12 @@ namespace Fin.Application.FinancialInstitutions;

public interface IFinancialInstitutionService
{
Task<FinancialInstitutionOutput> Get(Guid id);
Task<PagedOutput<FinancialInstitutionOutput>> GetList(FinancialInstitutionGetListInput input);
Task<FinancialInstitutionOutput> Create(FinancialInstitutionInput input, bool autoSave = false);
Task<bool> Update(Guid id, FinancialInstitutionInput input, bool autoSave = false);
Task<bool> Delete(Guid id, bool autoSave = false);
Task<bool> ToggleInactive(Guid id, bool autoSave = false);
public Task<FinancialInstitutionOutput> Get(Guid id);
public Task<PagedOutput<FinancialInstitutionOutput>> GetList(FinancialInstitutionGetListInput input);
public Task<FinancialInstitutionOutput> Create(FinancialInstitutionInput input, bool autoSave = false);
public Task<bool> Update(Guid id, FinancialInstitutionInput input, bool autoSave = false);
public Task<bool> Delete(Guid id, bool autoSave = false);
public Task<bool> ToggleInactive(Guid id, bool autoSave = false);
}

public class FinancialInstitutionService(
Expand Down Expand Up @@ -70,18 +71,20 @@ public async Task<bool> Update(Guid id, FinancialInstitutionInput input, bool au
public async Task<bool> Delete(Guid id, bool autoSave = false)
{
var institution = await repository.Query()
.Include(f => f.Wallets)
.FirstOrDefaultAsync(f => f.Id == id);
if (institution == null) return false;

if (institution == null || institution.Wallets.Any()) return false;
await repository.DeleteAsync(institution, autoSave);
return true;
}

public async Task<bool> ToggleInactive(Guid id, bool autoSave = false)
{
var institution = await repository.Query()
.Include(f => f.Wallets)
.FirstOrDefaultAsync(f => f.Id == id);
if (institution == null) return false;
if (institution == null || (!institution.Inactive && institution.Wallets.Any(w => !w.Inactivated))) return false;

institution.ToggleInactive();

Expand Down
3 changes: 2 additions & 1 deletion Fin.Application/Globals/Dtos/ValidationResultDto.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,17 @@

public class ValidationResultDto<D, E>
{
public D? Data { get; set; }

Check warning on line 5 in Fin.Application/Globals/Dtos/ValidationResultDto.cs

View workflow job for this annotation

GitHub Actions / build-and-test

The annotation for nullable reference types should only be used in code within a '#nullable' annotations context.
public string Message { get; set; }
public bool Success { get; set; }
public E? ErrorCode { get; set; }

Check warning on line 8 in Fin.Application/Globals/Dtos/ValidationResultDto.cs

View workflow job for this annotation

GitHub Actions / build-and-test

The annotation for nullable reference types should only be used in code within a '#nullable' annotations context.
}

public class ValidationResultDto<D>
public class ValidationResultDto<D>
{
public D? Data { get; set; }
public string Message { get; set; }
public bool Success { get; set; }
public Enum? ErrorCode { get; set; }
}

6 changes: 3 additions & 3 deletions Fin.Application/Menus/MenuService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -55,15 +55,15 @@ public async Task<List<MenuOutput>> GetListForSideNav()

public async Task<MenuOutput> Create(MenuInput input, bool autoSave = false)
{
ValidarInput(input);
ValidateInput(input);
var menu = new Menu(input);
await repository.AddAsync(menu, autoSave);
return new MenuOutput(menu);
}

public async Task<bool> Update(Guid id, MenuInput input, bool autoSave = false)
{
ValidarInput(input);
ValidateInput(input);
var menu = await repository.Query()
.FirstOrDefaultAsync(u => u.Id == id);
if (menu == null) return false;
Expand All @@ -84,7 +84,7 @@ public async Task<bool> Delete(Guid id, bool autoSave = false)
return true;
}

private static void ValidarInput( MenuInput input)
private static void ValidateInput( MenuInput input)
{
if (string.IsNullOrWhiteSpace(input.FrontRoute))
throw new BadHttpRequestException("FrontRoute is required");
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
namespace Fin.Application.TitleCategories.Enums;

public enum TitleCategoryCreateOrUpdateErrorCode
{
NameIsRequired = 0,
NameAlreadyInUse = 1,
NameTooLong = 2,
ColorIsRequired = 3,
ColorTooLong = 4,
IconIsRequired = 5,
IconTooLong = 6,
TitleCategoryNotFound = 7
}
Loading
Loading