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
6 changes: 6 additions & 0 deletions .idea/.idea.Fin-Backend/.idea/copilot.data.migration.ask.xml

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

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

6 changes: 6 additions & 0 deletions .idea/.idea.Fin-Backend/.idea/copilot.data.migration.edit.xml

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

6 changes: 6 additions & 0 deletions .idea/.idea.Fin-Backend/.idea/db-forest-config.xml

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

6 changes: 5 additions & 1 deletion Fin-Backend.sln.DotSettings.user
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
<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_003ACallerIdentifier_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002Econfig_003FJetBrains_003FRider2025_002E3_003Fresharper_002Dhost_003FSourcesCache_003F71f2ec7617c4a98abe150a490d848fca9f9da94acbda7b12e237ec81dcd3_003FCallerIdentifier_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>
Expand All @@ -11,9 +12,12 @@
<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;
<s:String x:Key="/Default/Environment/UnitTesting/UnitTestSessionStore/Sessions/=5f989a48_002Defa4_002D493c_002D924b_002D2fab4b9713fd/@EntryIndexedValue">&lt;SessionState ContinuousTestingMode="0" 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/Environment/UnitTesting/UnitTestSessionStore/Sessions/=6d201e39_002D0766_002D4794_002Dab10_002D09c6f9f8b4fa/@EntryIndexedValue">&lt;SessionState ContinuousTestingMode="0" IsActive="True" Name="MailSenderClientTest" xmlns="urn:schemas-jetbrains-com:jetbrains-ut-session"&gt;
&lt;Project Location="/home/rafaelchicovis/git/fin-backend/Fin.Test" Presentation="&amp;lt;Fin.Test&amp;gt;" /&gt;
&lt;/SessionState&gt;</s:String>



Expand Down
6 changes: 6 additions & 0 deletions Fin.Api/appsettings.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,12 @@
"Redis": "localhost:6379",
"Version": "EXEMPLE",
"EmailSender": {
"MailService": "MailKit",

"MailSenderApiKey": "EXEMPLE",
"MailSenderName": "FinApp Team",
"MailSenderAddress": "EXEMPLE",

"EmailAddress": "EXEMPLE",
"Password": "EXEMPLE"
},
Expand Down
26 changes: 21 additions & 5 deletions Fin.Application/Authentications/Services/AuthenticationService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
using Fin.Infrastructure.Constants;
using Fin.Infrastructure.Database.Repositories;
using Fin.Infrastructure.EmailSenders;
using Fin.Infrastructure.EmailSenders.Dto;
using Fin.Infrastructure.Redis;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Caching.Distributed;
Expand Down Expand Up @@ -68,7 +69,7 @@ public AuthenticationService(
public async Task SendResetPasswordEmail(SendResetPasswordEmailInput input)
{
var encryptedEmail = _cryptoHelper.Encrypt(input.Email);
var credential = _credentialRepository.Query()
var credential = _credentialRepository
.Include(c => c.User)
.FirstOrDefault(c => c.EncryptedEmail == encryptedEmail);

Expand All @@ -89,13 +90,28 @@ public async Task SendResetPasswordEmail(SendResetPasswordEmailInput input)
var logoIconUrl = $"{frontUrl}/icons/fin.png";
var resetLink = $"{frontUrl}/authentication/reset-password?token={token}";

var body = AuthenticationTemplates.ResetPasswordEmailTempalte
var subject = AuthenticationTemplates.ResetPasswordEmailSubject
.Replace("{{appName}}", AppConstants.AppName);

var plainBody = AuthenticationTemplates.ResetPasswordEmailPlainTemplate
.Replace("{{appName}}", AppConstants.AppName)
.Replace("{{linkLifeTime}}", tokenLifeTimeInHours.ToString())
.Replace("{{resetLink}}", resetLink);

var htmlBody = AuthenticationTemplates.ResetPasswordEmailTemplate
.Replace("{{appName}}", AppConstants.AppName)
.Replace("{{logoIconUrl}}", logoIconUrl)
.Replace("{{linkLifeTime}}", tokenLifeTimeInHours.ToString())
.Replace("{{resetLink}}", resetLink);

await _emailSender.SendEmailAsync(input.Email, "Fin - Reset Password", body);
await _emailSender.SendEmailAsync(new SendEmailDto
{
Subject = subject,
ToEmail = input.Email,
PlainBody = plainBody,
HtmlBody = htmlBody,
ToName = credential.User.DisplayName
});
}

public async Task<ValidationResultDto<bool, ResetPasswordErrorCode>> ResetPassword(ResetPasswordInput input)
Expand All @@ -117,7 +133,7 @@ public async Task<ValidationResultDto<bool, ResetPasswordErrorCode>> ResetPasswo
ErrorCode = ResetPasswordErrorCode.NotSamePassword
};

var credential = await _credentialRepository.Query().FirstOrDefaultAsync(c => c.ResetToken == input.ResetToken);
var credential = await _credentialRepository.FirstOrDefaultAsync(c => c.ResetToken == input.ResetToken);
if (credential == null)
return new ValidationResultDto<bool, ResetPasswordErrorCode>
{
Expand Down Expand Up @@ -156,7 +172,7 @@ public async Task<LoginWithGoogleOutput> LoginOrSingInWithGoogle(LoginWithGoogle
{
var encryptedEmail = _cryptoHelper.Encrypt(input.Email);

var credential = _credentialRepository.Query()
var credential = _credentialRepository
.Include(c => c.User)
.ThenInclude(c => c.Tenants)
.FirstOrDefault(c => c.EncryptedEmail == encryptedEmail);
Expand Down
13 changes: 12 additions & 1 deletion Fin.Application/Authentications/Utils/AuthenticationTemplates.cs
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,18 @@ public static class AuthenticationTemplates
</html>
";

public const string ResetPasswordEmailTempalte = @"
public const string ResetPasswordEmailSubject = "{{appName}} - Reset Your Password";

public const string ResetPasswordEmailPlainTemplate = @"
{{appName}} - Password Reset
We received a request to reset your password.
To create a new password, please copy and paste the link below into your browser:
{{resetLink}}
This link expires in {{linkLifeTime}} hours.
If you didn't request this, please ignore this email.
";

public const string ResetPasswordEmailTemplate = @"
<!DOCTYPE html>
<html lang='en'>
<head>
Expand Down
10 changes: 5 additions & 5 deletions Fin.Application/CardBrands/CardBrandService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -28,14 +28,14 @@ IAmbientData ambientData
{
public async Task<CardBrandOutput> Get(Guid id)
{
var entity = await repository.Query()
var entity = await repository
.FirstOrDefaultAsync(n => n.Id == id);
return entity != null ? new CardBrandOutput(entity) : null;
}

public async Task<PagedOutput<CardBrandOutput>> GetList(PagedFilteredAndSortedInput input)
{
return await repository.Query(false)
return await repository.AsNoTracking()
.OrderBy(m => m.Name)
.ApplyFilterAndSorter(input)
.Select(n => new CardBrandOutput(n))
Expand All @@ -44,7 +44,7 @@ public async Task<PagedOutput<CardBrandOutput>> GetList(PagedFilteredAndSortedIn

public async Task<List<CardBrandOutput>> GetListForSideNav()
{
return await repository.Query(false)
return await repository.AsNoTracking()
.OrderBy(m => m.Name)
.Select(m => new CardBrandOutput(m))
.ToListAsync();
Expand All @@ -61,7 +61,7 @@ public async Task<CardBrandOutput> Create(CardBrandInput input, bool autoSave =
public async Task<bool> Update(Guid id, CardBrandInput input, bool autoSave = false)
{
ValidarInput(input);
var cardBrand = await repository.Query()
var cardBrand = await repository
.FirstOrDefaultAsync(u => u.Id == id);
if (cardBrand == null) return false;

Expand All @@ -73,7 +73,7 @@ public async Task<bool> Update(Guid id, CardBrandInput input, bool autoSave = fa

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

Expand Down
10 changes: 5 additions & 5 deletions Fin.Application/CreditCards/Services/CreditCardService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -28,13 +28,13 @@ ICreditCardValidationService validationService
{
public async Task<CreditCardOutput> Get(Guid id)
{
var entity = await repository.Query(false).FirstOrDefaultAsync(n => n.Id == id);
var entity = await repository.AsNoTracking().FirstOrDefaultAsync(n => n.Id == id);
return entity != null ? new CreditCardOutput(entity) : null;
}

public async Task<PagedOutput<CreditCardOutput>> GetList(CreditCardGetListInput input)
{
return await repository.Query(false)
return await repository.AsNoTracking()
.WhereIf(input.Inactivated.HasValue, n => n.Inactivated == input.Inactivated.Value)
.WhereIf(input.CardBrandIds.Any(), creditCard => input.CardBrandIds.Contains(creditCard.CardBrandId))
.WhereIf(input.DebitWalletIds.Any(), creditCard => input.DebitWalletIds.Contains(creditCard.DebitWalletId))
Expand Down Expand Up @@ -62,7 +62,7 @@ public async Task<ValidationResultDto<bool, CreditCardCreateOrUpdateErrorCode>>
var validation = await validationService.ValidateInput<bool>(input, id);
if (!validation.Success) return validation;

var creditCard = await repository.Query().FirstAsync(u => u.Id == id);
var creditCard = await repository.FirstAsync(u => u.Id == id);
creditCard.Update(input);
await repository.UpdateAsync(creditCard, autoSave);

Expand All @@ -75,7 +75,7 @@ public async Task<ValidationResultDto<bool, CreditCardDeleteErrorCode>> Delete(G
var validation = await validationService.ValidateDelete(id);
if (!validation.Success) return validation;

var creditCard = await repository.Query().FirstAsync(u => u.Id == id);
var creditCard = await repository.FirstAsync(u => u.Id == id);
await repository.DeleteAsync(creditCard, autoSave);

validation.Success = true;
Expand All @@ -87,7 +87,7 @@ public async Task<ValidationResultDto<bool, CreditCardToggleInactiveErrorCode>>
var validation = await validationService.ValidateToggleInactive(id);
if (!validation.Success) return validation;

var creditCard = await repository.Query().FirstAsync(u => u.Id == id);
var creditCard = await repository.FirstAsync(u => u.Id == id);
creditCard.ToggleInactivated();
await repository.UpdateAsync(creditCard, autoSave);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ public async Task<ValidationResultDto<bool, CreditCardToggleInactiveErrorCode>>
{
var validationResult = new ValidationResultDto<bool, CreditCardToggleInactiveErrorCode>();

var creditCard = await repository.Query(tracking: false).FirstOrDefaultAsync(n => n.Id == creditCardId);
var creditCard = await repository.AsNoTracking().FirstOrDefaultAsync(n => n.Id == creditCardId);
if (creditCard is null)
{
validationResult.ErrorCode = CreditCardToggleInactiveErrorCode.CreditCardNotFound;
Expand All @@ -47,7 +47,7 @@ public async Task<ValidationResultDto<bool, CreditCardDeleteErrorCode>> Validate
{
var validationResult = new ValidationResultDto<bool, CreditCardDeleteErrorCode>();

var creditCardExists = await repository.Query().AnyAsync(n => n.Id == creditCardId);
var creditCardExists = await repository.AnyAsync(n => n.Id == creditCardId);
if (!creditCardExists)
{
validationResult.ErrorCode = CreditCardDeleteErrorCode.CreditCardNotFound;
Expand All @@ -68,7 +68,7 @@ public async Task<ValidationResultDto<T, CreditCardCreateOrUpdateErrorCode>> Val

if (editingId.HasValue)
{
var creditCardExists = await repository.Query()
var creditCardExists = await repository
.AnyAsync(n => n.Id == editingId.Value);
if (!creditCardExists)
{
Expand Down Expand Up @@ -120,7 +120,7 @@ public async Task<ValidationResultDto<T, CreditCardCreateOrUpdateErrorCode>> Val
return validationResult;
}

var nameAlredInUse = await repository.Query()
var nameAlredInUse = await repository
.AnyAsync(n => n.Name.ToLower() == input.Name.ToLower() && (!editingId.HasValue || n.Id != editingId));
if (nameAlredInUse)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,14 +27,14 @@ IRepository<FinancialInstitution> repository
{
public async Task<FinancialInstitutionOutput> Get(Guid id)
{
var entity = await repository.Query(false)
var entity = await repository.AsNoTracking()
.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)
return await repository.AsNoTracking()
.WhereIf(input.Inactive.HasValue, f => f.Inactive == input.Inactive.Value)
.WhereIf(input.Type.HasValue, f => f.Type == input.Type.Value)
.OrderBy(f => f.Inactive)
Expand All @@ -56,7 +56,7 @@ public async Task<FinancialInstitutionOutput> Create(FinancialInstitutionInput i
public async Task<bool> Update(Guid id, FinancialInstitutionInput input, bool autoSave = false)
{
await ValidateInput(input, id);
var institution = await repository.Query()
var institution = await repository
.FirstOrDefaultAsync(f => f.Id == id);
if (institution == null) return false;

Expand All @@ -70,7 +70,7 @@ 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()
var institution = await repository
.Include(f => f.Wallets)
.FirstOrDefaultAsync(f => f.Id == id);
if (institution == null || institution.Wallets.Any()) return false;
Expand All @@ -81,7 +81,7 @@ public async Task<bool> Delete(Guid id, bool autoSave = false)

public async Task<bool> ToggleInactive(Guid id, bool autoSave = false)
{
var institution = await repository.Query()
var institution = await repository
.Include(f => f.Wallets)
.FirstOrDefaultAsync(f => f.Id == id);
if (institution == null || (!institution.Inactive && institution.Wallets.Any(w => !w.Inactivated))) return false;
Expand Down Expand Up @@ -112,7 +112,7 @@ private async Task ValidateInput(FinancialInstitutionInput input, Guid? editingI
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()
var existingName = await repository
.Where(f => f.Name == input.Name)
.WhereIf(editingId.HasValue, f => f.Id != editingId.Value)
.AnyAsync();
Expand Down
Loading
Loading