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
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ CodeCoverage/
*.VisualState.xml
TestResult.xml
nunit-*.xml
notifications.md


.vs
appsettings.Development.json
2 changes: 1 addition & 1 deletion Fin.Api/Fin.Api.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@

<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.Authentication.Google" Version="9.0.4" />
<PackageReference Include="Microsoft.AspNetCore.OpenApi" Version="9.0.4"/>
<PackageReference Include="Microsoft.AspNetCore.OpenApi" Version="9.0.4" />
<PackageReference Include="Microsoft.Extensions.Caching.StackExchangeRedis" Version="9.0.2" />
<PackageReference Include="NSwag.AspNetCore" Version="14.4.0" />
<PackageReference Include="StackExchange.Redis" Version="2.8.31" />
Expand Down
24 changes: 24 additions & 0 deletions Fin.Api/Fin.Api.sln
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 17
VisualStudioVersion = 17.5.2.0
MinimumVisualStudioVersion = 10.0.40219.1
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Fin.Api", "Fin.Api.csproj", "{330E38C3-8925-05A6-F8DE-2312E9F69666}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{330E38C3-8925-05A6-F8DE-2312E9F69666}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{330E38C3-8925-05A6-F8DE-2312E9F69666}.Debug|Any CPU.Build.0 = Debug|Any CPU
{330E38C3-8925-05A6-F8DE-2312E9F69666}.Release|Any CPU.ActiveCfg = Release|Any CPU
{330E38C3-8925-05A6-F8DE-2312E9F69666}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {85E7AB78-8C20-4A98-9130-CD73E0B54F27}
EndGlobalSection
EndGlobal
53 changes: 53 additions & 0 deletions Fin.Api/FinancialInstitutions/FinancialInstitutionController.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
using Fin.Application.FinancialInstitutions;
using Fin.Domain.FinancialInstitutions.Dtos;
using Fin.Domain.Global.Classes;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;

namespace Fin.Api.FinancialInstitutions;

[Route("financial-institutions")]
[Authorize]
public class FinancialInstitutionController(IFinancialInstitutionService service): ControllerBase
{
[HttpGet]
public async Task<PagedOutput<FinancialInstitutionOutput>> GetList([FromQuery] FinancialInstitutionGetListInput input)
{
return await service.GetList(input);
}

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

[HttpPost]
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}")]
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}")]
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")]
public async Task<ActionResult> ToggleInactive([FromRoute] Guid id)
{
var toggled = await service.ToggleInactive(id, autoSave: true);
return toggled ? Ok() : NotFound();
}
}
2 changes: 1 addition & 1 deletion Fin.Api/appsettings.json
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

WHY DELETED THIS?

Original file line number Diff line number Diff line change
Expand Up @@ -40,4 +40,4 @@
"Url": "http://localhost:4200"
}
}
}
}
24 changes: 24 additions & 0 deletions Fin.Application/Fin.Application.sln
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 17
VisualStudioVersion = 17.5.2.0
MinimumVisualStudioVersion = 10.0.40219.1
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Fin.Application", "Fin.Application.csproj", "{930F4674-00C7-367F-2ABF-37898BCDDD08}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{930F4674-00C7-367F-2ABF-37898BCDDD08}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{930F4674-00C7-367F-2ABF-37898BCDDD08}.Debug|Any CPU.Build.0 = Debug|Any CPU
{930F4674-00C7-367F-2ABF-37898BCDDD08}.Release|Any CPU.ActiveCfg = Release|Any CPU
{930F4674-00C7-367F-2ABF-37898BCDDD08}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {5C017504-FB39-43D3-9270-899D293A0EB6}
EndGlobalSection
EndGlobal
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
using Fin.Domain.FinancialInstitutions.Enums;
using Fin.Domain.Global.Classes;

public class FinancialInstitutionGetListInput : PagedFilteredAndSortedInput
{
public bool? Inactive { get; set; }
public FinancialInstitutionType? Type { get; set; }

}
120 changes: 120 additions & 0 deletions Fin.Application/FinancialInstitutions/FinancialInstitutionService.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
using Fin.Domain.FinancialInstitutions.Dtos;
using Fin.Domain.FinancialInstitutions.Entities;
using Fin.Domain.Global.Classes;
using Fin.Infrastructure.AmbientDatas;
using Fin.Infrastructure.AutoServices.Interfaces;
using Fin.Infrastructure.Database.Extensions;
using Fin.Infrastructure.Database.Repositories;
using Microsoft.AspNetCore.Http;
using Microsoft.EntityFrameworkCore;

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 class FinancialInstitutionService(
IRepository<FinancialInstitution> repository
) : IFinancialInstitutionService, IAutoTransient
{
public async Task<FinancialInstitutionOutput> Get(Guid id)
{
var entity = await repository.Query(false)
.FirstOrDefaultAsync(f => f.Id == id);
return entity != null ? new FinancialInstitutionOutput(entity) : null;
}

public async Task<PagedOutput<FinancialInstitutionOutput>> GetList(FinancialInstitutionGetListInput input)
{
return await repository.Query(false)
.WhereIf(input.Inactive.HasValue, f => f.Inactive == input.Inactive.Value)
.WhereIf(input.Type.HasValue, f => f.Type == input.Type.Value)
.OrderBy(f => f.Inactive)
.ThenBy(f => f.Name)
.ApplyFilterAndSorter(input)
.Select(f => new FinancialInstitutionOutput(f))
.ToPagedResult(input);
}

public async Task<FinancialInstitutionOutput> Create(FinancialInstitutionInput input, bool autoSave = false)
{
await ValidateInput(input);

var institution = new FinancialInstitution(input);
await repository.AddAsync(institution, autoSave);
return new FinancialInstitutionOutput(institution);
}

public async Task<bool> Update(Guid id, FinancialInstitutionInput input, bool autoSave = false)
{
await ValidateInput(input, id);
var institution = await repository.Query()
.FirstOrDefaultAsync(f => f.Id == id);
if (institution == null) return false;



institution.Update(input);
await repository.UpdateAsync(institution, autoSave);

return true;
}

public async Task<bool> Delete(Guid id, bool autoSave = false)
{
var institution = await repository.Query()
.FirstOrDefaultAsync(f => f.Id == id);
if (institution == null) return false;

await repository.DeleteAsync(institution, autoSave);
return true;
}

public async Task<bool> ToggleInactive(Guid id, bool autoSave = false)
{
var institution = await repository.Query()
.FirstOrDefaultAsync(f => f.Id == id);
if (institution == null) return false;

institution.ToggleInactive();

await repository.UpdateAsync(institution, autoSave);
return true;
}

private async Task ValidateInput(FinancialInstitutionInput input, Guid? editingId = null)
{

if (string.IsNullOrWhiteSpace(input.Name))
throw new BadHttpRequestException("Name is required");
if (input.Name.Count() > 100)
throw new BadHttpRequestException("Name must be at most 100 characters long");
if (string.IsNullOrWhiteSpace(input.Icon))
throw new BadHttpRequestException("Icon is required");
if (input.Icon.Count() > 20)
throw new BadHttpRequestException("Icon must be at most 20 characters long");

if (string.IsNullOrWhiteSpace(input.Color))
throw new BadHttpRequestException("Color is required");
if (input.Color.Count() > 20)
throw new BadHttpRequestException("Color must be at most 20 characters long");

if(!string.IsNullOrWhiteSpace(input.Code) && input.Code.Count() > 15)
throw new BadHttpRequestException("Code must be at most 15 characters long");

var existingName = await repository.Query()
.Where(f => f.Name == input.Name)
.WhereIf(editingId.HasValue, f => f.Id != editingId.Value)
.AnyAsync();

if (existingName)
throw new BadHttpRequestException("A financial institution with this name already exists");
}
}
25 changes: 25 additions & 0 deletions Fin.Domain/FinancialInstitutions/Dtos/FinancialInstitutionInput.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
using System.ComponentModel.DataAnnotations;
using Fin.Domain.FinancialInstitutions.Enums;

namespace Fin.Domain.FinancialInstitutions.Dtos;

public class FinancialInstitutionInput
{
[Required]
[MaxLength(100)]
public string Name { get; set; }

[MaxLength(15)]
public string Code { get; set; }

[Required]
public FinancialInstitutionType Type { get; set; }

[MaxLength(20)]
[Required]
public string Icon { get; set; }

[MaxLength(20)]
[Required]
public string Color { get; set; }
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
using Fin.Domain.FinancialInstitutions.Entities;
using Fin.Domain.FinancialInstitutions.Enums;

namespace Fin.Domain.FinancialInstitutions.Dtos;

public class FinancialInstitutionOutput
{
public Guid Id { get; set; }
public string Name { get; set; }
public string Code { get; set; }
public FinancialInstitutionType Type { get; set; }
public string Icon { get; set; }
public string Color { get; set; }
public bool Inactive { get; set; }

public FinancialInstitutionOutput()
{
}

public FinancialInstitutionOutput(FinancialInstitution input)
{
Id = input.Id;
Name = input.Name;
Code = input.Code;
Type = input.Type;
Icon = input.Icon;
Color = input.Color;
Inactive = input.Inactive;
}
}
44 changes: 44 additions & 0 deletions Fin.Domain/FinancialInstitutions/Entities/FinancialInstitution.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
using Fin.Domain.FinancialInstitutions.Dtos;
using Fin.Domain.FinancialInstitutions.Enums;
using Fin.Domain.Global.Interfaces;

namespace Fin.Domain.FinancialInstitutions.Entities;

public class FinancialInstitution : IAuditedEntity
{
public string Name { get; set; }
public string Code { get; set; }
public FinancialInstitutionType Type { get; set; }
public string Icon { get; set; }
public string Color { get; set; }
public bool Inactive { get; set; }

public Guid Id { get; set; }
public Guid CreatedBy { get; set; }
public Guid UpdatedBy { get; set; }
public DateTime CreatedAt { get; set; }
public DateTime UpdatedAt { get; set; }
public FinancialInstitution()
{
}

public FinancialInstitution(FinancialInstitutionInput input)
{
Name = input.Name;
Code = input.Code;
Type = input.Type;
Icon = input.Icon;
Color = input.Color;
}

public void Update(FinancialInstitutionInput input)
{
Name = input.Name;
Code = input.Code;
Type = input.Type;
Icon = input.Icon;
Color = input.Color;
}

public void ToggleInactive() => Inactive = !Inactive;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
namespace Fin.Domain.FinancialInstitutions.Enums
{
public enum FinancialInstitutionType
{
Bank = 0,
DigitalBank = 1,
FoodCard = 2,
}
}
Loading
Loading