Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
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
7 changes: 7 additions & 0 deletions Fin-Backend.sln.DotSettings.user
Original file line number Diff line number Diff line change
@@ -1,7 +1,14 @@
<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_003ACancellationToken_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002Econfig_003FJetBrains_003FRider2025_002E2_003Fresharper_002Dhost_003FSourcesCache_003F3c32b6774a69464667352a9e262bc924c57674f811c382e29abe8dbe3639f4c_003FCancellationToken_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003ADecimal_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002Econfig_003FJetBrains_003FRider2025_002E2_003Fresharper_002Dhost_003FSourcesCache_003Fb926483dd9373e252824fb9f8bdf906c40ada3256d27f68392f486b2c1f6c_003FDecimal_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AFieldInfo_002ECoreCLR_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002Econfig_003FJetBrains_003FRider2025_002E2_003Fresharper_002Dhost_003FSourcesCache_003Fa4f51f201dca40bd938f37a528b1d91b4f1089a8ba612a057c4732bf7c21aad_003FFieldInfo_002ECoreCLR_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AList_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002Econfig_003FJetBrains_003FRider2025_002E2_003Fresharper_002Dhost_003FSourcesCache_003F1c3f7d5c03bf77051eb8a758fb3549c4e2d4cb3dc6f6d567a94a315044961e_003FList_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AList_00601_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002Econfig_003FJetBrains_003FRider2025_002E2_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003F1cb0ee423e6b413691aee222686aa63eb400_003F09_003F397dd16b_003FList_00601_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_003ANullable_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002Econfig_003FJetBrains_003FRider2025_002E2_003Fresharper_002Dhost_003FSourcesCache_003F6728f66657329080f0f419df519283a455cdca7d2617a05b18553b02da52fa_003FNullable_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_003ARuntimeType_002ECoreCLR_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002Econfig_003FJetBrains_003FRider2025_002E2_003Fresharper_002Dhost_003FSourcesCache_003Fbaf2eef7c7bbea3742b74ee71fe7168c3a8c2269a1ce22d51333175656840_003FRuntimeType_002ECoreCLR_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/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>
<s:String x:Key="/Default/Environment/UnitTesting/UnitTestSessionStore/Sessions/=5f989a48_002Defa4_002D493c_002D924b_002D2fab4b9713fd/@EntryIndexedValue">&lt;SessionState ContinuousTestingMode="0" IsActive="True" Name="All tests from Solution" xmlns="urn:schemas-jetbrains-com:jetbrains-ut-session"&gt;
Expand Down
54 changes: 54 additions & 0 deletions Fin.Api/Titles/TitleController.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
using Fin.Application.Titles.Dtos;
using Fin.Application.Titles.Enums;
using Fin.Application.Titles.Services;
using Fin.Domain.Global.Classes;
using Fin.Domain.Titles.Dtos;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;

namespace Fin.Api.Titles;

[Route("titles")]
[Authorize]
public class TitleController(ITitleService service) : ControllerBase
{
[HttpGet]
public async Task<PagedOutput<TitleOutput>> GetList([FromQuery] TitleGetListInput input)
{
return await service.GetList(input);
}

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

[HttpPost]
public async Task<ActionResult<TitleOutput>> Create([FromBody] TitleInput 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] TitleInput input)
{
var validationResult = await service.Update(id, input, autoSave: true);
return validationResult.Success ? Ok() :
validationResult.ErrorCode == TitleCreateOrUpdateErrorCode.TitleNotFound ? 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 == TitleDeleteErrorCode.TitleNotFound ? NotFound(validationResult) :
UnprocessableEntity(validationResult);
}
}
134 changes: 123 additions & 11 deletions Fin.Application/Globals/Dtos/ValidationResultDto.cs
Original file line number Diff line number Diff line change
@@ -1,18 +1,130 @@
namespace Fin.Application.Globals.Dtos;
#nullable enable
using Fin.Infrastructure.Errors;
using Fin.Infrastructure.ValidationsPipeline;

public class ValidationResultDto<D, E>
namespace Fin.Application.Globals.Dtos;

public class ValidationResultDto<TDSuccess, TDError, TErroCode> where TErroCode : struct, Enum
{
public TDSuccess? Data { get; set; }
public TDError? ErrorData { get; set; }
public TErroCode? ErrorCode { get; set; }

public bool Success
{
get
{
if (InternalSuccess.HasValue) return InternalSuccess.Value;
return ErrorCode == null;
}
set => InternalSuccess = value;
}

public string Message
{
get
{
if (!string.IsNullOrWhiteSpace(InternalMessage))
{
return InternalMessage;
}

if (Success) return "Success";

return ErrorCode.HasValue ? ErrorCode.Value.GetErrorMessage() : string.Empty;;
}
set => InternalMessage = value;
}

protected bool? InternalSuccess { get; set; }
protected string? InternalMessage { get; set; }

public ValidationResultDto<TDSuccess, TDError, TErroCode> WithError(TErroCode errorCode, string? message = null)
{
ErrorCode = errorCode;
InternalMessage = message;
return this;
}

public ValidationResultDto<TDSuccess, TDError, TErroCode> WithError(TErroCode errorCode, TDError errorData,
string? message = null)
{
ErrorCode = errorCode;
ErrorData = errorData;
InternalMessage = message;
return this;
}

public ValidationResultDto<TDSuccess, TDError, TErroCode> WithSuccess(TDSuccess successData)
{
Data = successData;
return this;
}

public static ValidationResultDto<TDSuccess, TDError, TErroCode> FromPipeline(
ValidationPipelineOutput<TErroCode, TDError> pipelineOutput)
{
return new ValidationResultDto<TDSuccess, TDError, TErroCode>
{
ErrorData = pipelineOutput.Data,
ErrorCode = pipelineOutput.Code,
};
}
}

public class ValidationResultDto<TDSuccess, TErroCode> : ValidationResultDto<TDSuccess, object, TErroCode>
where TErroCode : struct, Enum
{
public D? Data { get; set; }
public string Message { get; set; }
public bool Success { get; set; }
public E? ErrorCode { get; set; }
public new ValidationResultDto<TDSuccess, TErroCode> WithError(TErroCode errorCode, string? message = null)
{
ErrorCode = errorCode;
InternalMessage = message;
return this;
}

public new ValidationResultDto<TDSuccess, TErroCode> WithSuccess(TDSuccess successData)
{
Data = successData;
return this;
}

public static ValidationResultDto<TDSuccess, TErroCode> FromPipeline(
ValidationPipelineOutput<TErroCode> pipelineOutput)
{
return new ValidationResultDto<TDSuccess, TErroCode>
{
ErrorCode = pipelineOutput.Code,
};
}
}

public class ValidationResultDto<D>
public enum NoErrorCode
{
public D? Data { get; set; }
public string Message { get; set; }
public bool Success { get; set; }
public Enum? ErrorCode { get; set; }
None = 0
}

public class ValidationResultDto<TDSuccess> : ValidationResultDto<TDSuccess, object, NoErrorCode>
{
public new ValidationResultDto<TDSuccess> WithSuccess(TDSuccess successData)
{
Data = successData;
return this;
}
}

public static class ValidationResultDtoExtensions
{
public static ValidationResultDto<TSuccess, TError, TErrorCode> ToValidationResult<TSuccess, TError, TErrorCode>(
this ValidationPipelineOutput<TErrorCode, TError> pipeline)
where TErrorCode : struct, Enum
{
return ValidationResultDto<TSuccess, TError, TErrorCode>.FromPipeline(pipeline);
}

public static ValidationResultDto<TSuccess, TErrorCode> ToValidationResult<TSuccess, TErrorCode>(
this ValidationPipelineOutput<TErrorCode> pipeline)
where TErrorCode : struct, Enum
{
return ValidationResultDto<TSuccess, TErrorCode>.FromPipeline(pipeline);
}
}
13 changes: 13 additions & 0 deletions Fin.Application/Titles/Dtos/TitleGetListInput.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
using Fin.Domain.Global.Classes;
using Fin.Domain.Global.Enums;
using Fin.Domain.Titles.Enums;

namespace Fin.Application.Titles.Dtos;

public class TitleGetListInput: PagedFilteredAndSortedInput
{
public List<Guid> CategoryIds { get; set; } = [];
public MultiplyFilterOperator CategoryOperator { get; set; }
public List<Guid> WalletIds { get; set; } = [];
public TitleType? Type { get; set; }
}
39 changes: 39 additions & 0 deletions Fin.Application/Titles/Enums/TitleCreateOrUpdateErrorCode.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
using Fin.Infrastructure.Errors;

namespace Fin.Application.Titles.Enums;

public enum TitleCreateOrUpdateErrorCode
{
[ErrorMessage("Title not found")]
TitleNotFound = 0,

[ErrorMessage("Description must have less than 100 characters.")]
DescriptionTooLong = 1,

[ErrorMessage("Description is required.")]
DescriptionIsRequired = 2,

[ErrorMessage("Wallet not found")]
WalletNotFound = 3,

[ErrorMessage("Wallet is inactive")]
WalletInactive = 4,

[ErrorMessage("Title date must be equal or after wallet creation date.")]
TitleDateMustBeEqualOrAfterWalletCreation = 5,

[ErrorMessage("Some categories was not found")]
SomeCategoriesNotFound = 6,

[ErrorMessage("Some categories is inactive")]
SomeCategoriesInactive = 7,

[ErrorMessage("Some categories has incompatible types")]
SomeCategoriesHasIncompatibleTypes = 8,

[ErrorMessage("Value must be greater than zero.")]
ValueMustBeGraterThanZero = 9,

[ErrorMessage("Duplicated title in same date time until minute.")]
DuplicateTitleInSameDateTimeMinute = 10,
}
9 changes: 9 additions & 0 deletions Fin.Application/Titles/Enums/TitleDeleteErrorCode.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
using Fin.Infrastructure.Errors;

namespace Fin.Application.Titles.Enums;

public enum TitleDeleteErrorCode
{
[ErrorMessage("Title not found")]
TitleNotFound = 0,
}
Loading
Loading