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: 2 additions & 3 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.

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

This file was deleted.

9 changes: 7 additions & 2 deletions Fin-Backend.sln.DotSettings.user
Original file line number Diff line number Diff line change
@@ -1,4 +1,9 @@
<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/Environment/UnitTesting/UnitTestSessionStore/Sessions/=39a47a06_002D7915_002D4eea_002D991a_002D44cb9c74b08f/@EntryIndexedValue">&lt;SessionState ContinuousTestingMode="0" IsActive="True" Name="All tests from Solution" xmlns="urn:schemas-jetbrains-com:jetbrains-ut-session"&gt;
<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></wpf:ResourceDictionary>
&lt;/SessionState&gt;</s:String>

</wpf:ResourceDictionary>
54 changes: 54 additions & 0 deletions Fin.Api/TitleCategories/TitleCategoryController.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
using Fin.Application.TitleCategories;
using Fin.Application.TitleCategories.Dtos;
using Fin.Domain.Global.Classes;
using Fin.Domain.TitleCategories.Dtos;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;

namespace Fin.Api.TitleCategories;

[Route("title-categories")]
[Authorize]
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();
}

[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();
}

[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();
}

[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();
}

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

namespace Fin.Application.TitleCategories.Dtos;

public class TitleCategoryGetListInput: PagedFilteredAndSortedInput
{
public bool? Inactivated { get; set; }
public TitleCategoryType? Type { get; set; }
}
97 changes: 97 additions & 0 deletions Fin.Application/TitleCategories/TitleCategoryService.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
using Fin.Application.TitleCategories.Dtos;
using Fin.Domain.Global.Classes;
using Fin.Domain.TitleCategories.Dtos;
using Fin.Domain.TitleCategories.Entities;
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.TitleCategories;

public interface ITitleCategoryService
{
public Task<TitleCategoryOutput> Get(Guid id);
public Task<PagedOutput<TitleCategoryOutput>> GetList(TitleCategoryGetListInput input);
public Task<TitleCategoryOutput> Create(TitleCategoryInput input, bool autoSave = false);
public Task<bool> Update(Guid id, TitleCategoryInput input, bool autoSave = false);
public Task<bool> Delete(Guid id, bool autoSave = false);
public Task<bool> ToggleInactive(Guid id, bool autoSave = false);
}

public class TitleCategoryService(
IRepository<TitleCategory> repository
) : ITitleCategoryService, IAutoTransient
{
public async Task<TitleCategoryOutput> Get(Guid id)
{
var entity = await repository.Query(false).FirstOrDefaultAsync(n => n.Id == id);
return entity != null ? new TitleCategoryOutput(entity) : null;
}

public async Task<PagedOutput<TitleCategoryOutput>> GetList(TitleCategoryGetListInput input)
{
return await repository.Query(false)
.WhereIf(input.Inactivated.HasValue, n => n.Inactivated == input.Inactivated.Value)
.WhereIf(input.Type.HasValue, n => n.Type == input.Type.Value)
.OrderBy(m => m.Inactivated)
.ThenBy(m => m.Name)
.ApplyFilterAndSorter(input)
.Select(n => new TitleCategoryOutput(n))
.ToPagedResult(input);
}

public async Task<TitleCategoryOutput> Create(TitleCategoryInput input, bool autoSave = false)
{
ValidarInput(input);
var titleCategory = new TitleCategory(input);
await repository.AddAsync(titleCategory, autoSave);
return new TitleCategoryOutput(titleCategory);
}

public async Task<bool> Update(Guid id, TitleCategoryInput input, bool autoSave = false)
{
ValidarInput(input);
var titleCategory = await repository.Query()
.FirstOrDefaultAsync(u => u.Id == id);
if (titleCategory == null) return false;

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

return true;
}

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

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

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

titleCategory.ToggleInactivated();
await repository.UpdateAsync(titleCategory, autoSave);

return true;
}

private static void ValidarInput( TitleCategoryInput input)
{
if (string.IsNullOrWhiteSpace(input.Color))
throw new BadHttpRequestException("FrontRoute is required");
if (string.IsNullOrWhiteSpace(input.Name))
throw new BadHttpRequestException("Name is required");
if (string.IsNullOrWhiteSpace(input.Icon))
throw new BadHttpRequestException("Icon is required");
}
}
4 changes: 1 addition & 3 deletions Fin.Domain/Global/Interfaces/IAuditedEntity.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,4 @@
using System.ComponentModel.DataAnnotations;

namespace Fin.Domain.Global.Interfaces;
namespace Fin.Domain.Global.Interfaces;

public interface IAuditedEntity: IEntity
{
Expand Down
19 changes: 19 additions & 0 deletions Fin.Domain/TitleCategories/Dtos/TitleCategoryInput.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
using System.ComponentModel.DataAnnotations;
using Fin.Domain.TitleCategories.Enums;

namespace Fin.Domain.TitleCategories.Dtos;

public class TitleCategoryInput
{
[Required]
public string Name { get; set; }

[Required]
public string Color { get; set; }
[Required]

public string Icon { get; set; }

[Required]
public TitleCategoryType Type { get; set; }
}
19 changes: 19 additions & 0 deletions Fin.Domain/TitleCategories/Dtos/TitleCategoryOutput.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
using Fin.Domain.TitleCategories.Entities;
using Fin.Domain.TitleCategories.Enums;

namespace Fin.Domain.TitleCategories.Dtos;

public class TitleCategoryOutput(TitleCategory titleCategory)
{
public Guid Id { get; set; } = titleCategory.Id;
public bool Inactivated { get; set; } = titleCategory.Inactivated;
public string Name { get; set; } = titleCategory.Name;
public string Color { get; set; } = titleCategory.Color;
public string Icon { get; set; } = titleCategory.Icon;
public TitleCategoryType Type { get; set; } = titleCategory.Type;

public TitleCategoryOutput(): this(new TitleCategory())
{
}

}
42 changes: 42 additions & 0 deletions Fin.Domain/TitleCategories/Entities/TitleCategory.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
using Fin.Domain.Global.Interfaces;
using Fin.Domain.TitleCategories.Dtos;
using Fin.Domain.TitleCategories.Enums;

namespace Fin.Domain.TitleCategories.Entities;

public class TitleCategory: IAuditedTenantEntity
{
public bool Inactivated { get; private set; }
public string Name { get; private set; }
public string Color { get; private set; }
public string Icon { get; private set; }
public TitleCategoryType Type { get; private 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 Guid TenantId { get; set; }

public TitleCategory()
{
}
public TitleCategory(TitleCategoryInput input)
{
Name = input.Name;
Color = input.Color;
Icon = input.Icon;
Type = input.Type;
}

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

public void ToggleInactivated() => Inactivated = !Inactivated;
}
8 changes: 8 additions & 0 deletions Fin.Domain/TitleCategories/Enums/TitleCategoryType.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
namespace Fin.Domain.TitleCategories.Enums;

public enum TitleCategoryType: byte
{
Expense = 0,
Income = 1,
Both = 2
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
using Fin.Domain.TitleCategories.Entities;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Metadata.Builders;

namespace Fin.Infrastructure.Database.Configurations.TitleCategories;

public class TitleCategoryConfiguration : IEntityTypeConfiguration<TitleCategory>
{
public void Configure(EntityTypeBuilder<TitleCategory> builder)
{
builder.HasKey(x => x.Id);

builder.Property(x => x.Name).HasMaxLength(100).IsRequired();
builder.Property(x => x.Icon).HasMaxLength(20).IsRequired();
builder.Property(x => x.Color).HasMaxLength(20).IsRequired();
}
}
9 changes: 7 additions & 2 deletions Fin.Infrastructure/Database/FinDbContext.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
using Fin.Domain.Notifications;
using Fin.Domain.Notifications.Entities;
using Fin.Domain.Tenants.Entities;
using Fin.Domain.TitleCategories.Entities;
using Fin.Domain.Users.Entities;
using Fin.Infrastructure.AmbientDatas;
using Fin.Infrastructure.Database.Configurations;
Expand All @@ -27,6 +28,8 @@ public class FinDbContext : DbContext
public DbSet<NotificationUserDelivery> NotificationUserDeliveries { get; set; }

public DbSet<Menu> Menus { get; set; }

public DbSet<TitleCategory> TitleCategories { get; set; }

private readonly IAmbientData _ambientData;

Expand All @@ -39,6 +42,8 @@ public FinDbContext(DbContextOptions<FinDbContext> options, IAmbientData ambient
{
_ambientData = ambientData;
}



protected override void OnModelCreating(ModelBuilder modelBuilder)
{
Expand All @@ -61,7 +66,6 @@ protected override void OnConfiguring(DbContextOptionsBuilder options)

private void ApplyTenantFilter(ModelBuilder modelBuilder)
{
if (!(_ambientData?.IsLogged ?? false)) return;
foreach (var entityType in modelBuilder.Model.GetEntityTypes())
{
if (typeof(ITenantEntity).IsAssignableFrom(entityType.ClrType))
Expand All @@ -77,7 +81,8 @@ private void ApplyTenantFilter(ModelBuilder modelBuilder)

private void SetTenantFilter<TEntity>(ModelBuilder modelBuilder) where TEntity : class, ITenantEntity
{
modelBuilder.Entity<TEntity>().HasQueryFilter(e => e.TenantId == _ambientData.TenantId);
if (Database.ProviderName == "Microsoft.EntityFrameworkCore.Sqlite") return;
modelBuilder.Entity<TEntity>().HasQueryFilter(e => _ambientData.IsLogged && e.TenantId == _ambientData.TenantId);
}

private void ApplyUtcConverterToDateTime(ModelBuilder modelBuilder)
Expand Down
Loading
Loading