diff --git a/Fin-Backend.sln.DotSettings.user b/Fin-Backend.sln.DotSettings.user
index 18eaaeb..5036aa5 100644
--- a/Fin-Backend.sln.DotSettings.user
+++ b/Fin-Backend.sln.DotSettings.user
@@ -2,8 +2,10 @@
ForceIncluded
ForceIncluded
ForceIncluded
+ ForceIncluded
ForceIncluded
ForceIncluded
+ ForceIncluded
ForceIncluded
ForceIncluded
ForceIncluded
@@ -20,6 +22,7 @@
<Project Location="/home/rafaelchicovis/git/fin-backend/Fin.Test" Presentation="<Fin.Test>" />
</SessionState>
True
+
False
True
False
diff --git a/Fin.Api/appsettings.json b/Fin.Api/appsettings.json
index b2ab7e9..6a2c99d 100644
--- a/Fin.Api/appsettings.json
+++ b/Fin.Api/appsettings.json
@@ -6,7 +6,8 @@
}
},
"ConnectionStrings": {
- "DefaultConnection": "Host=localhost;Port=5432;Database=fin_app;Username=fin_app;Password=fin_app"
+ "DefaultConnection": "Host=localhost;Port=5432;Database=fin_app;Username=fin_app;Password=fin_app",
+ "MongoDbConnection": "mongodb://localhost:27017"
},
"AllowedHosts": "*",
"ApiSettings": {
diff --git a/Fin.Domain/CardBrands/Entities/CardBrand.cs b/Fin.Domain/CardBrands/Entities/CardBrand.cs
index 48b8989..269e6dc 100644
--- a/Fin.Domain/CardBrands/Entities/CardBrand.cs
+++ b/Fin.Domain/CardBrands/Entities/CardBrand.cs
@@ -4,7 +4,7 @@
namespace Fin.Domain.CardBrands.Entities;
-public class CardBrand: IAuditedEntity
+public class CardBrand: IAuditedEntity, ILoggable
{
public string Name { get; set; }
public string Icon { get; set; }
@@ -36,4 +36,19 @@ public void Update(CardBrandInput input)
Color = input.Color;
}
+
+ public object GetLog()
+ {
+ return new
+ {
+ Id,
+ CreatedAt,
+ CreatedBy,
+ UpdatedAt,
+ UpdatedBy,
+ Name,
+ Color,
+ Icon
+ };
+ }
}
diff --git a/Fin.Domain/CreditCards/Entities/CreditCard.cs b/Fin.Domain/CreditCards/Entities/CreditCard.cs
index 81f94fd..a7ed055 100644
--- a/Fin.Domain/CreditCards/Entities/CreditCard.cs
+++ b/Fin.Domain/CreditCards/Entities/CreditCard.cs
@@ -6,7 +6,7 @@
namespace Fin.Domain.CreditCards.Entities;
-public class CreditCard: IAuditedTenantEntity
+public class CreditCard: ILoggableAuditedTenantEntity
{
public string Name { get; private set; }
public string Color { get; private set; }
@@ -63,4 +63,25 @@ public void Update(CreditCardInput input)
}
public void ToggleInactivated() => Inactivated = !Inactivated;
+
+ public object GetLog()
+ {
+ return new
+ {
+ Id,
+ CreatedAt,
+ CreatedBy,
+ UpdatedAt,
+ UpdatedBy,
+ TenantId,
+ Name,
+ Color,
+ Icon,
+ Limit,
+ DueDay,
+ ClosingDay,
+ DebitWalletId,
+ FinancialInstitutionId,
+ };
+ }
}
\ No newline at end of file
diff --git a/Fin.Domain/Fin.Domain.csproj b/Fin.Domain/Fin.Domain.csproj
index 6005f43..7b8dbdc 100644
--- a/Fin.Domain/Fin.Domain.csproj
+++ b/Fin.Domain/Fin.Domain.csproj
@@ -5,8 +5,4 @@
enable
-
-
-
-
diff --git a/Fin.Domain/FinancialInstitutions/Entities/FinancialInstitution.cs b/Fin.Domain/FinancialInstitutions/Entities/FinancialInstitution.cs
index f7d2dd3..2af52d3 100644
--- a/Fin.Domain/FinancialInstitutions/Entities/FinancialInstitution.cs
+++ b/Fin.Domain/FinancialInstitutions/Entities/FinancialInstitution.cs
@@ -1,12 +1,13 @@
using Fin.Domain.CreditCards.Entities;
using Fin.Domain.FinancialInstitutions.Dtos;
using Fin.Domain.FinancialInstitutions.Enums;
+using Fin.Domain.Global.Decorators;
using Fin.Domain.Global.Interfaces;
using Fin.Domain.Wallets.Entities;
namespace Fin.Domain.FinancialInstitutions.Entities;
-public class FinancialInstitution : IAuditedEntity
+public class FinancialInstitution : IAuditedEntity, ILoggable
{
public string Name { get; set; }
public string Code { get; set; }
@@ -47,4 +48,21 @@ public void Update(FinancialInstitutionInput input)
}
public void ToggleInactive() => Inactive = !Inactive;
+ public object GetLog()
+ {
+ return new
+ {
+ Id,
+ CreatedAt,
+ CreatedBy,
+ UpdatedAt,
+ UpdatedBy,
+ Code,
+ Icon,
+ Color,
+ Inactive,
+ Type,
+ TypeDescription = Type.GetTranslateKey(),
+ };
+ }
}
diff --git a/Fin.Domain/FinancialInstitutions/Enums/FinancialInstitutionType.cs b/Fin.Domain/FinancialInstitutions/Enums/FinancialInstitutionType.cs
index a45eb91..b422d6d 100644
--- a/Fin.Domain/FinancialInstitutions/Enums/FinancialInstitutionType.cs
+++ b/Fin.Domain/FinancialInstitutions/Enums/FinancialInstitutionType.cs
@@ -1,9 +1,14 @@
+using Fin.Domain.Global.Decorators;
+
namespace Fin.Domain.FinancialInstitutions.Enums
{
public enum FinancialInstitutionType
{
+ [FrontTranslateKey("finCore.features.financialInstitutions.type.bank")]
Bank = 0,
+ [FrontTranslateKey("finCore.features.financialInstitutions.type.digitalBank")]
DigitalBank = 1,
+ [FrontTranslateKey("finCore.features.financialInstitutions.type.foodCard")]
FoodCard = 2,
}
}
diff --git a/Fin.Domain/Global/Decorators/FrontTranslateKeyAttribute.cs b/Fin.Domain/Global/Decorators/FrontTranslateKeyAttribute.cs
new file mode 100644
index 0000000..812cf17
--- /dev/null
+++ b/Fin.Domain/Global/Decorators/FrontTranslateKeyAttribute.cs
@@ -0,0 +1,32 @@
+using System.Reflection;
+
+namespace Fin.Domain.Global.Decorators;
+
+[AttributeUsage(AttributeTargets.Field)]
+public class FrontTranslateKeyAttribute(string translateKey): Attribute
+{
+ public string TranslateKey { get; } = translateKey;
+}
+
+
+public static class FrontTranslateKeyExtension
+{
+ public static string GetTranslateKey(this T valeu, bool throwIfNotFoundMessage = true)
+ {
+ var type = valeu.GetType();
+ var memberInfo = type.GetMember(valeu.ToString() ?? string.Empty);
+
+ if (memberInfo.Length > 0)
+ {
+ var attribute = memberInfo[0].GetCustomAttribute();
+ if (attribute != null)
+ {
+ return attribute.TranslateKey;
+ }
+ }
+
+ return throwIfNotFoundMessage
+ ? throw new ArgumentException($"Cannot get translate key {valeu}")
+ : string.Empty;
+ }
+}
\ No newline at end of file
diff --git a/Fin.Domain/Global/Interfaces/ILoggable.cs b/Fin.Domain/Global/Interfaces/ILoggable.cs
new file mode 100644
index 0000000..bd466e6
--- /dev/null
+++ b/Fin.Domain/Global/Interfaces/ILoggable.cs
@@ -0,0 +1,6 @@
+namespace Fin.Domain.Global.Interfaces;
+
+public interface ILoggable
+{
+ object GetLog();
+}
\ No newline at end of file
diff --git a/Fin.Domain/Global/Interfaces/ILoggableAuditedTenantEntity.cs b/Fin.Domain/Global/Interfaces/ILoggableAuditedTenantEntity.cs
new file mode 100644
index 0000000..92718bf
--- /dev/null
+++ b/Fin.Domain/Global/Interfaces/ILoggableAuditedTenantEntity.cs
@@ -0,0 +1,6 @@
+namespace Fin.Domain.Global.Interfaces;
+
+public interface ILoggableAuditedTenantEntity: ILoggable, IAuditedEntity, ITenantEntity
+{
+
+}
\ No newline at end of file
diff --git a/Fin.Domain/Menus/Entities/Menu.cs b/Fin.Domain/Menus/Entities/Menu.cs
index be250db..40145e1 100644
--- a/Fin.Domain/Menus/Entities/Menu.cs
+++ b/Fin.Domain/Menus/Entities/Menu.cs
@@ -1,10 +1,11 @@
-using Fin.Domain.Global.Interfaces;
+using Fin.Domain.Global.Decorators;
+using Fin.Domain.Global.Interfaces;
using Fin.Domain.Menus.Dtos;
using Fin.Domain.Menus.Enums;
namespace Fin.Domain.Menus.Entities;
-public class Menu: IAuditedEntity
+public class Menu: IAuditedEntity, ILoggable
{
public string FrontRoute { get; set; }
public string Name { get; set; }
@@ -45,4 +46,24 @@ public void Update(MenuInput input)
OnlyForAdmin = input.OnlyForAdmin;
Position = input.Position;
}
+
+ public object GetLog()
+ {
+ return new
+ {
+ Id,
+ CreatedAt,
+ CreatedBy,
+ UpdatedAt,
+ UpdatedBy,
+ FrontRoute,
+ Name,
+ Icon,
+ Color,
+ KeyWords,
+ OnlyForAdmin,
+ Position,
+ PositionDescription = Position.GetTranslateKey()
+ };
+ }
}
diff --git a/Fin.Domain/Menus/Enums/MenuPosition.cs b/Fin.Domain/Menus/Enums/MenuPosition.cs
index 955a527..2a3674c 100644
--- a/Fin.Domain/Menus/Enums/MenuPosition.cs
+++ b/Fin.Domain/Menus/Enums/MenuPosition.cs
@@ -1,8 +1,13 @@
-namespace Fin.Domain.Menus.Enums;
+using Fin.Domain.Global.Decorators;
+
+namespace Fin.Domain.Menus.Enums;
public enum MenuPosition
{
+ [FrontTranslateKey("finCore.features.menus.hide")]
Hide = 0,
+ [FrontTranslateKey("finCore.features.menus.leftTop")]
LeftTop = 1,
+ [FrontTranslateKey("finCore.features.menus.leftBottom")]
LeftBottom = 2
}
\ No newline at end of file
diff --git a/Fin.Domain/Notifications/Entities/Notification.cs b/Fin.Domain/Notifications/Entities/Notification.cs
index f12be5c..412efc8 100644
--- a/Fin.Domain/Notifications/Entities/Notification.cs
+++ b/Fin.Domain/Notifications/Entities/Notification.cs
@@ -1,4 +1,5 @@
using System.Collections.ObjectModel;
+using Fin.Domain.Global.Decorators;
using Fin.Domain.Global.Extensions;
using Fin.Domain.Global.Interfaces;
using Fin.Domain.Notifications.Dtos;
@@ -6,7 +7,7 @@
namespace Fin.Domain.Notifications.Entities;
-public class Notification: IAuditedEntity
+public class Notification: IAuditedEntity, ILoggable
{
public List Ways { get; set; } = [];
public string TextBody { get; set; }
@@ -90,4 +91,29 @@ public List UpdateAndReturnToRemoveDeliveries(Notifica
return deliveriesToDelete;
}
+
+ public object GetLog()
+ {
+ return new
+ {
+ Id,
+ CreatedAt,
+ CreatedBy,
+ UpdatedAt,
+ UpdatedBy,
+ Ways,
+ WaysDescriptions = Ways.Select(way => way.GetTranslateKey()),
+ TextBody,
+ HtmlBody,
+ NormalizedTextBody,
+ Title,
+ Continuous,
+ NormalizedTitle,
+ StartToDelivery,
+ StopToDelivery,
+ Link,
+ Severity,
+ SeverityDescriptions = Severity.GetTranslateKey(),
+ };
+ }
}
\ No newline at end of file
diff --git a/Fin.Domain/Notifications/Entities/NotificationUserDelivery.cs b/Fin.Domain/Notifications/Entities/NotificationUserDelivery.cs
index 21cf2b4..e750299 100644
--- a/Fin.Domain/Notifications/Entities/NotificationUserDelivery.cs
+++ b/Fin.Domain/Notifications/Entities/NotificationUserDelivery.cs
@@ -1,8 +1,9 @@
-using Fin.Domain.Users.Entities;
+using Fin.Domain.Global.Interfaces;
+using Fin.Domain.Users.Entities;
namespace Fin.Domain.Notifications.Entities;
-public class NotificationUserDelivery
+public class NotificationUserDelivery: ILoggable
{
public Guid NotificationId { get; set; }
public Guid UserId { get; set; }
@@ -33,4 +34,16 @@ public void MarkAsVisualized()
{
Visualized = true;
}
+
+ public object GetLog()
+ {
+ return new
+ {
+ NotificationId,
+ UserId,
+ Delivery,
+ Visualized,
+ BackgroundJobId
+ };
+ }
}
\ No newline at end of file
diff --git a/Fin.Domain/Notifications/Entities/UserNotificationSettings.cs b/Fin.Domain/Notifications/Entities/UserNotificationSettings.cs
index 205121d..601b140 100644
--- a/Fin.Domain/Notifications/Entities/UserNotificationSettings.cs
+++ b/Fin.Domain/Notifications/Entities/UserNotificationSettings.cs
@@ -1,11 +1,12 @@
-using Fin.Domain.Global.Interfaces;
+using Fin.Domain.Global.Decorators;
+using Fin.Domain.Global.Interfaces;
using Fin.Domain.Notifications.Dtos;
using Fin.Domain.Notifications.Enums;
using Fin.Domain.Users.Entities;
namespace Fin.Domain.Notifications.Entities;
-public class UserNotificationSettings: IAuditedTenantEntity
+public class UserNotificationSettings: ILoggableAuditedTenantEntity
{
public Guid UserId { get; set; }
public bool Enabled { get; set; }
@@ -61,4 +62,22 @@ public void RemoveTokens(List tokens)
{
FirebaseTokens = FirebaseTokens.Except(tokens).ToList();
}
+
+ public object GetLog()
+ {
+ return new
+ {
+ Id,
+ CreatedAt,
+ CreatedBy,
+ UpdatedAt,
+ UpdatedBy,
+ TenantId,
+ UserId,
+ Enabled,
+ AllowedWays,
+ AllowedWaysDesciptions = AllowedWays.Select(way => way.GetTranslateKey()),
+ FirebaseTokens
+ };
+ }
}
\ No newline at end of file
diff --git a/Fin.Domain/Notifications/Entities/UserRememberUseSetting.cs b/Fin.Domain/Notifications/Entities/UserRememberUseSetting.cs
index 3a52436..43aeeef 100644
--- a/Fin.Domain/Notifications/Entities/UserRememberUseSetting.cs
+++ b/Fin.Domain/Notifications/Entities/UserRememberUseSetting.cs
@@ -1,11 +1,12 @@
-using Fin.Domain.Global.Interfaces;
+using Fin.Domain.Global.Decorators;
+using Fin.Domain.Global.Interfaces;
using Fin.Domain.Notifications.Dtos;
using Fin.Domain.Notifications.Enums;
using Fin.Domain.Users.Entities;
namespace Fin.Domain.Notifications.Entities;
-public class UserRememberUseSetting : IAuditedTenantEntity
+public class UserRememberUseSetting : ILoggableAuditedTenantEntity
{
public Guid UserId { get; set; }
public List Ways { get; set; } = new();
@@ -42,4 +43,22 @@ public void Update(UserRememberUseSettingInput input)
WeekDays = input.WeekDays;
NotifyOn = input.NotifyOn;
}
+
+ public object GetLog()
+ {
+ return new
+ {
+ Id,
+ CreatedAt,
+ CreatedBy,
+ UpdatedAt,
+ UpdatedBy,
+ TenantId,
+ UserId,
+ Ways,
+ WaysDescriptions = Ways.Select(way => way.GetTranslateKey()),
+ NotifyOn,
+ WeekDays,
+ };
+ }
}
\ No newline at end of file
diff --git a/Fin.Domain/Notifications/Enums/NotificationSeverity.cs b/Fin.Domain/Notifications/Enums/NotificationSeverity.cs
index 6964b79..6564bcc 100644
--- a/Fin.Domain/Notifications/Enums/NotificationSeverity.cs
+++ b/Fin.Domain/Notifications/Enums/NotificationSeverity.cs
@@ -1,10 +1,17 @@
+using Fin.Domain.Global.Decorators;
+
namespace Fin.Domain.Notifications.Enums;
public enum NotificationSeverity
{
+ [FrontTranslateKey("finCore.features.notifications.severity.default")]
Default = 0,
+ [FrontTranslateKey("finCore.features.notifications.severity.success")]
Success = 1,
+ [FrontTranslateKey("finCore.features.notifications.severity.error")]
Error = 2,
+ [FrontTranslateKey("finCore.features.notifications.severity.warning")]
Warning = 3,
+ [FrontTranslateKey("finCore.features.notifications.severity.info")]
Info = 4,
}
\ No newline at end of file
diff --git a/Fin.Domain/Notifications/Enums/NotificationWay.cs b/Fin.Domain/Notifications/Enums/NotificationWay.cs
index 231b117..cf0dac4 100644
--- a/Fin.Domain/Notifications/Enums/NotificationWay.cs
+++ b/Fin.Domain/Notifications/Enums/NotificationWay.cs
@@ -1,9 +1,15 @@
-namespace Fin.Domain.Notifications.Enums;
+using Fin.Domain.Global.Decorators;
+
+namespace Fin.Domain.Notifications.Enums;
public enum NotificationWay
{
+ [FrontTranslateKey("finCore.features.notifications.ways.snack")]
Snack = 0,
+ [FrontTranslateKey("finCore.features.notifications.ways.message")]
Message = 1,
+ [FrontTranslateKey("finCore.features.notifications.ways.push")]
Push = 2,
+ [FrontTranslateKey("finCore.features.notifications.ways.email")]
Email = 3
}
\ No newline at end of file
diff --git a/Fin.Domain/People/Entities/Person.cs b/Fin.Domain/People/Entities/Person.cs
index bea8b17..cb46587 100644
--- a/Fin.Domain/People/Entities/Person.cs
+++ b/Fin.Domain/People/Entities/Person.cs
@@ -4,7 +4,7 @@
namespace Fin.Domain.People.Entities;
-public class Person: IAuditedTenantEntity
+public class Person: ILoggableAuditedTenantEntity
{
public string Name { get; private set; }
public bool Inactivated { get; private set; }
@@ -37,4 +37,19 @@ public void ToggleInactivated()
{
Inactivated = !Inactivated;
}
+
+ public object GetLog()
+ {
+ return new
+ {
+ Id,
+ CreatedAt,
+ CreatedBy,
+ UpdatedAt,
+ UpdatedBy,
+ TenantId,
+ Name,
+ Inactivated
+ };
+ }
}
\ No newline at end of file
diff --git a/Fin.Domain/People/Entities/TitlePerson.cs b/Fin.Domain/People/Entities/TitlePerson.cs
index 11e2709..8d348e5 100644
--- a/Fin.Domain/People/Entities/TitlePerson.cs
+++ b/Fin.Domain/People/Entities/TitlePerson.cs
@@ -4,7 +4,7 @@
namespace Fin.Domain.People.Entities;
-public class TitlePerson: ITenant, IAudited
+public class TitlePerson: ITenant, IAudited, ILoggable
{
public Guid PersonId { get; private set; }
public virtual Person Person { get; set; }
@@ -35,4 +35,19 @@ public void Update(decimal percentage)
public Guid UpdatedBy { get; set; }
public DateTime CreatedAt { get; set; }
public DateTime UpdatedAt { get; set; }
+
+ public object GetLog()
+ {
+ return new
+ {
+ CreatedAt,
+ CreatedBy,
+ UpdatedAt,
+ UpdatedBy,
+ TenantId,
+ PersonId,
+ TitleId,
+ Percentage
+ };
+ }
}
\ No newline at end of file
diff --git a/Fin.Domain/Tenants/Entities/Tenant.cs b/Fin.Domain/Tenants/Entities/Tenant.cs
index e838a60..1ad6ccf 100644
--- a/Fin.Domain/Tenants/Entities/Tenant.cs
+++ b/Fin.Domain/Tenants/Entities/Tenant.cs
@@ -3,7 +3,7 @@
namespace Fin.Domain.Tenants.Entities;
-public class Tenant: IEntity
+public class Tenant: IEntity, ILoggable
{
public Guid Id { get; set; }
public DateTime CreatedAt { get; private set; }
@@ -25,5 +25,17 @@ public Tenant(DateTime now, string timezone, string locale)
UpdatedAt = now;
Locale = locale ?? "pt-BR";
Timezone = timezone ?? "America/Sao_Paulo";
- }
+ }
+
+ public object GetLog()
+ {
+ return new
+ {
+ Id,
+ CreatedAt,
+ UpdatedAt,
+ Locale,
+ Timezone
+ };
+ }
}
\ No newline at end of file
diff --git a/Fin.Domain/Tenants/Entities/TenantUser.cs b/Fin.Domain/Tenants/Entities/TenantUser.cs
index b1ab27f..bd9f489 100644
--- a/Fin.Domain/Tenants/Entities/TenantUser.cs
+++ b/Fin.Domain/Tenants/Entities/TenantUser.cs
@@ -1,7 +1,18 @@
-namespace Fin.Domain.Tenants.Entities;
+using Fin.Domain.Global.Interfaces;
-public class TenantUser
+namespace Fin.Domain.Tenants.Entities;
+
+public class TenantUser: ILoggable
{
public Guid TenantId { get; set; }
public Guid UserId { get; set; }
+
+ public object GetLog()
+ {
+ return new
+ {
+ TenantId,
+ UserId
+ };
+ }
}
\ No newline at end of file
diff --git a/Fin.Domain/TitleCategories/Entities/TitleCategory.cs b/Fin.Domain/TitleCategories/Entities/TitleCategory.cs
index 94d18b6..a5210e6 100644
--- a/Fin.Domain/TitleCategories/Entities/TitleCategory.cs
+++ b/Fin.Domain/TitleCategories/Entities/TitleCategory.cs
@@ -1,3 +1,4 @@
+using Fin.Domain.Global.Decorators;
using Fin.Domain.Global.Interfaces;
using Fin.Domain.TitleCategories.Dtos;
using Fin.Domain.TitleCategories.Enums;
@@ -5,7 +6,7 @@
namespace Fin.Domain.TitleCategories.Entities;
-public class TitleCategory: IAuditedTenantEntity
+public class TitleCategory: ILoggableAuditedTenantEntity
{
public bool Inactivated { get; private set; }
public string Name { get; private set; }
@@ -43,4 +44,23 @@ public void Update(TitleCategoryInput input)
}
public void ToggleInactivated() => Inactivated = !Inactivated;
+
+ public object GetLog()
+ {
+ return new
+ {
+ Id,
+ CreatedAt,
+ CreatedBy,
+ UpdatedAt,
+ UpdatedBy,
+ TenantId,
+ Inactivated,
+ Name,
+ Icon,
+ Color,
+ Type,
+ TypeDescription = Type.GetTranslateKey(),
+ };
+ }
}
\ No newline at end of file
diff --git a/Fin.Domain/TitleCategories/Entities/TitleTitleCategory.cs b/Fin.Domain/TitleCategories/Entities/TitleTitleCategory.cs
index a47326c..baca2ad 100644
--- a/Fin.Domain/TitleCategories/Entities/TitleTitleCategory.cs
+++ b/Fin.Domain/TitleCategories/Entities/TitleTitleCategory.cs
@@ -1,8 +1,9 @@
+using Fin.Domain.Global.Interfaces;
using Fin.Domain.Titles.Entities;
namespace Fin.Domain.TitleCategories.Entities;
-public class TitleTitleCategory
+public class TitleTitleCategory: ILoggable
{
public Guid TitleId { get; set; }
public virtual Title Title { get; set; }
@@ -19,4 +20,13 @@ public TitleTitleCategory(Guid categoryId, Guid titleId)
TitleId = titleId;
TitleCategoryId = categoryId;
}
+
+ public object GetLog()
+ {
+ return new
+ {
+ TitleId,
+ TitleCategoryId
+ };
+ }
}
\ No newline at end of file
diff --git a/Fin.Domain/TitleCategories/Enums/TitleCategoryType.cs b/Fin.Domain/TitleCategories/Enums/TitleCategoryType.cs
index 630c43c..1758610 100644
--- a/Fin.Domain/TitleCategories/Enums/TitleCategoryType.cs
+++ b/Fin.Domain/TitleCategories/Enums/TitleCategoryType.cs
@@ -1,11 +1,15 @@
+using Fin.Domain.Global.Decorators;
using Fin.Domain.Titles.Enums;
namespace Fin.Domain.TitleCategories.Enums;
public enum TitleCategoryType: byte
{
+ [FrontTranslateKey("finCore.features.titleCategory.type.expense")]
Expense = 0,
+ [FrontTranslateKey("finCore.features.titleCategory.type.income")]
Income = 1,
+ [FrontTranslateKey("finCore.features.titleCategory.type.both")]
Both = 2
}
diff --git a/Fin.Domain/Titles/Entities/Title.cs b/Fin.Domain/Titles/Entities/Title.cs
index 63c17a7..879891a 100644
--- a/Fin.Domain/Titles/Entities/Title.cs
+++ b/Fin.Domain/Titles/Entities/Title.cs
@@ -1,4 +1,5 @@
using System.Collections.ObjectModel;
+using Fin.Domain.Global.Decorators;
using Fin.Domain.Global.Interfaces;
using Fin.Domain.People.Dtos;
using Fin.Domain.People.Entities;
@@ -9,9 +10,10 @@
namespace Fin.Domain.Titles.Entities;
-public class Title: IAuditedTenantEntity
+public class Title: IAuditedTenantEntity, ILoggable
{
public decimal Value { get; set; }
+
public TitleType Type { get; set; }
public string Description { get; set; }
@@ -143,4 +145,27 @@ public List SyncPeopleAndReturnToRemove(List tit
return titlePeopleToDelete;
}
+
+ public object GetLog()
+ {
+ return new
+ {
+ Id = Id,
+ Date = Date,
+ Description = Description,
+
+ Type = Type,
+ TypeDescription = Type.GetTranslateKey(),
+ OriginalValue = Value,
+ EffectiveValue = EffectiveValue,
+ PreviousBalance = PreviousBalance,
+ ResultingBalance = ResultingBalance,
+
+ WalletId = WalletId,
+ TenantId = TenantId,
+
+ CreatedBy = CreatedBy,
+ CreatedAt = CreatedAt
+ };
+ }
}
\ No newline at end of file
diff --git a/Fin.Domain/Titles/Enums/TitleType.cs b/Fin.Domain/Titles/Enums/TitleType.cs
index 6beb346..e0d2cb7 100644
--- a/Fin.Domain/Titles/Enums/TitleType.cs
+++ b/Fin.Domain/Titles/Enums/TitleType.cs
@@ -1,7 +1,13 @@
+using System.ComponentModel;
+using Fin.Domain.Global.Decorators;
+
namespace Fin.Domain.Titles.Enums;
public enum TitleType: byte
{
+ [FrontTranslateKey("finCore.features.title.type.expense")]
Expense = 0,
+
+ [FrontTranslateKey("finCore.features.title.type.income")]
Income = 1
}
\ No newline at end of file
diff --git a/Fin.Domain/Users/Entities/User.cs b/Fin.Domain/Users/Entities/User.cs
index 107bcb8..ecb9e10 100644
--- a/Fin.Domain/Users/Entities/User.cs
+++ b/Fin.Domain/Users/Entities/User.cs
@@ -5,7 +5,7 @@
namespace Fin.Domain.Users.Entities;
-public class User: IEntity
+public class User: IEntity, ILoggable
{
public Guid Id { get; set; }
@@ -88,4 +88,19 @@ public void MakeAdmin()
{
IsAdmin = true;
}
+
+ public object GetLog()
+ {
+ return new
+ {
+ Id,
+ FirstName,
+ LastName,
+ DisplayName,
+ CreatedAt,
+ UpdatedAt,
+ IsAdmin,
+ IsActivity
+ };
+ }
}
\ No newline at end of file
diff --git a/Fin.Domain/Users/Entities/UserDeleteRequest.cs b/Fin.Domain/Users/Entities/UserDeleteRequest.cs
index ffe18f3..3fdae09 100644
--- a/Fin.Domain/Users/Entities/UserDeleteRequest.cs
+++ b/Fin.Domain/Users/Entities/UserDeleteRequest.cs
@@ -2,7 +2,7 @@
namespace Fin.Domain.Users.Entities;
-public class UserDeleteRequest : IAuditedEntity
+public class UserDeleteRequest : IAuditedEntity, ILoggable
{
public Guid UserId { get; set; }
public virtual User User { get; set; }
@@ -44,4 +44,21 @@ public void Abort(Guid userAbortedId, DateTime abortedAt)
if (!User.IsActivity) User.ToggleActivity();
}
+ public object GetLog()
+ {
+ return new
+ {
+ Id,
+ CreatedAt,
+ CreatedBy,
+ UpdatedAt,
+ UpdatedBy,
+ UserId,
+ UserAbortedId,
+ AbortedAt,
+ Aborted,
+ DeleteRequestedAt,
+ DeleteEffectivatedAt
+ };
+ }
}
\ No newline at end of file
diff --git a/Fin.Domain/Wallets/Entities/Wallet.cs b/Fin.Domain/Wallets/Entities/Wallet.cs
index 4934bf7..a1abc06 100644
--- a/Fin.Domain/Wallets/Entities/Wallet.cs
+++ b/Fin.Domain/Wallets/Entities/Wallet.cs
@@ -7,7 +7,7 @@
namespace Fin.Domain.Wallets.Entities;
-public class Wallet: IAuditedTenantEntity
+public class Wallet: ILoggableAuditedTenantEntity
{
public Guid Id { get; set; }
public Guid CreatedBy { get; set; }
@@ -66,4 +66,23 @@ public decimal CalculateBalanceAt(DateTime dateTime)
return lastTitle?.ResultingBalance ?? InitialBalance;
}
+
+ public object GetLog()
+ {
+ return new
+ {
+ Id,
+ CreatedBy,
+ CreatedAt,
+ UpdatedAt,
+ UpdatedBy,
+ TenantId,
+ Name,
+ Color,
+ Icon,
+ Inactivated,
+ FinancialInstitutionId,
+ InitialBalance
+ };
+ }
}
\ No newline at end of file
diff --git a/Fin.Infrastructure/Audits/AuditEntry.cs b/Fin.Infrastructure/Audits/AuditEntry.cs
new file mode 100644
index 0000000..06b4235
--- /dev/null
+++ b/Fin.Infrastructure/Audits/AuditEntry.cs
@@ -0,0 +1,19 @@
+using Fin.Domain.Global.Interfaces;
+using Fin.Infrastructure.AmbientDatas;
+using Fin.Infrastructure.Audits.Enums;
+using Fin.Infrastructure.Audits.Interfaces;
+using Microsoft.EntityFrameworkCore.ChangeTracking;
+
+namespace Fin.Infrastructure.Audits;
+
+public class AuditEntry(IAmbientData ambientData)
+{
+ public string EntityName { get; set; }
+ public string EntityId { get; set; }
+ public object NewValue { get; set; }
+ public object OldValue { get; set; }
+
+ public AuditLogAction Action { get; set; }
+ public Guid UserId { get; set; } = ambientData.UserId.GetValueOrDefault();
+ public Guid TenantId { get; set; } = ambientData.TenantId.GetValueOrDefault();
+}
\ No newline at end of file
diff --git a/Fin.Infrastructure/Audits/AuditLogDocument.cs b/Fin.Infrastructure/Audits/AuditLogDocument.cs
new file mode 100644
index 0000000..e20a572
--- /dev/null
+++ b/Fin.Infrastructure/Audits/AuditLogDocument.cs
@@ -0,0 +1,24 @@
+using Fin.Infrastructure.Audits.Enums;
+using MongoDB.Bson;
+using MongoDB.Bson.Serialization.Attributes;
+
+namespace Fin.Infrastructure.Audits;
+
+public class AuditLogDocument
+{
+ [BsonId]
+ [BsonRepresentation(BsonType.ObjectId)]
+ public string InternalId { get; set; }
+
+ public string EntityName { get; set; }
+ public string EntityId { get; set; }
+
+ public AuditLogAction Action { get; set; }
+ public DateTime DateTime { get; set; }
+
+ public object NewValue { get; set; }
+ public object OldValue { get; set; }
+
+ public Guid UserId { get; set; }
+ public Guid TenantId { get; set; }
+}
\ No newline at end of file
diff --git a/Fin.Infrastructure/Audits/AuditLogExtensions.cs b/Fin.Infrastructure/Audits/AuditLogExtensions.cs
new file mode 100644
index 0000000..4ca34d0
--- /dev/null
+++ b/Fin.Infrastructure/Audits/AuditLogExtensions.cs
@@ -0,0 +1,33 @@
+using Fin.Infrastructure.Audits.Interfaces;
+using Microsoft.Extensions.Configuration;
+using Microsoft.Extensions.DependencyInjection;
+using MongoDB.Bson;
+using MongoDB.Bson.Serialization;
+using MongoDB.Bson.Serialization.Serializers;
+using MongoDB.Driver;
+
+namespace Fin.Infrastructure.Audits;
+
+public static class AuditLogExtensions
+{
+ public static IServiceCollection AddAuditLog(this IServiceCollection services, IConfiguration configuration)
+ {
+ try
+ {
+ BsonSerializer.RegisterSerializer(new GuidSerializer(BsonType.String));
+ }
+ catch (BsonSerializationException) {}
+
+ var settings = MongoClientSettings.FromConnectionString(configuration.GetConnectionString("MongoDbConnection"));
+ var mongoClient = new MongoClient(settings);
+
+ services.AddSingleton(_ => mongoClient);
+ services.AddScoped(sp => sp.GetRequiredService().GetDatabase("LogsDB"));
+ services.AddScoped();
+
+ services.AddScoped();
+
+
+ return services;
+ }
+}
\ No newline at end of file
diff --git a/Fin.Infrastructure/Audits/AuditLogInterceptor.cs b/Fin.Infrastructure/Audits/AuditLogInterceptor.cs
new file mode 100644
index 0000000..1ebf06a
--- /dev/null
+++ b/Fin.Infrastructure/Audits/AuditLogInterceptor.cs
@@ -0,0 +1,156 @@
+using System.Text.Json;
+using Fin.Domain.Global.Interfaces;
+using Fin.Infrastructure.AmbientDatas;
+using Fin.Infrastructure.Audits.Enums;
+using Fin.Infrastructure.Audits.Interfaces;
+using Microsoft.EntityFrameworkCore;
+using Microsoft.EntityFrameworkCore.ChangeTracking;
+using Microsoft.EntityFrameworkCore.Diagnostics;
+
+namespace Fin.Infrastructure.Audits;
+
+public class AuditLogInterceptor(
+ IAuditLogService auditService,
+ IAmbientData ambientData
+) : SaveChangesInterceptor
+{
+ private List _pendingLogs;
+
+ public override InterceptionResult SavingChanges(
+ DbContextEventData eventData,
+ InterceptionResult result)
+ {
+ CaptureChanges(eventData.Context);
+ return base.SavingChanges(eventData, result);
+ }
+
+ public override async ValueTask> SavingChangesAsync(
+ DbContextEventData eventData,
+ InterceptionResult result,
+ CancellationToken cancellationToken = default)
+ {
+ CaptureChanges(eventData.Context);
+ return await base.SavingChangesAsync(eventData, result, cancellationToken);
+ }
+
+ public override int SavedChanges(SaveChangesCompletedEventData eventData, int result)
+ {
+ SaveLogs();
+ return base.SavedChanges(eventData, result);
+ }
+
+ public override async ValueTask SavedChangesAsync(
+ SaveChangesCompletedEventData eventData,
+ int result,
+ CancellationToken cancellationToken = default)
+ {
+ await SaveLogsAsync();
+ return await base.SavedChangesAsync(eventData, result, cancellationToken);
+ }
+
+ private void CaptureChanges(DbContext context)
+ {
+ _pendingLogs = new List();
+
+ var entries = context.ChangeTracker.Entries()
+ .Where(e => e.Entity is ILoggable &&
+ (e.State == EntityState.Added ||
+ e.State == EntityState.Modified ||
+ e.State == EntityState.Deleted))
+ .ToList();
+
+ foreach (var entry in entries)
+ {
+ if (entry.Entity is not ILoggable loggable) continue;
+
+ var entityType = entry.Entity.GetType();
+ var entityName = entityType.Name;
+
+ var logEntry = new AuditEntry(ambientData)
+ {
+ EntityName = entityName,
+ EntityId = GetEntityId(entry),
+ };
+
+ switch (entry.State)
+ {
+ case EntityState.Added:
+ logEntry.Action = AuditLogAction.Created;
+ logEntry.NewValue = loggable.GetLog();
+ logEntry.OldValue = null;
+ break;
+
+ case EntityState.Modified:
+ logEntry.Action = AuditLogAction.Updated;
+ logEntry.NewValue = loggable.GetLog();
+ logEntry.OldValue = GetOriginalValues(entry);
+ break;
+
+ case EntityState.Deleted:
+ logEntry.Action = AuditLogAction.Deleted;
+ logEntry.NewValue = null;
+ logEntry.OldValue = loggable.GetLog();
+ break;
+ }
+
+ _pendingLogs.Add(logEntry);
+ }
+ }
+
+ private string GetEntityId(EntityEntry entry)
+ {
+ var keyValues = entry.Properties
+ .Where(p => p.Metadata.IsKey())
+ .Select(p => new { p.Metadata.Name, Value = p.CurrentValue })
+ .ToList();
+
+ if (keyValues.Count == 1)
+ {
+ return keyValues[0].Value?.ToString() ?? string.Empty;
+ }
+
+ var compositeKey = keyValues.ToDictionary(
+ k => k.Name,
+ k => k.Value
+ );
+ return JsonSerializer.Serialize(compositeKey);
+ }
+
+ private object GetOriginalValues(EntityEntry entry)
+ {
+ var loggable = entry.Entity as ILoggable;
+ if (loggable == null) return null;
+
+ var originalEntity = Activator.CreateInstance(entry.Entity.GetType());
+
+ foreach (var property in entry.Properties)
+ {
+ var propInfo = entry.Entity.GetType().GetProperty(property.Metadata.Name);
+ if (propInfo != null && propInfo.CanWrite)
+ {
+ propInfo.SetValue(originalEntity, property.OriginalValue);
+ }
+ }
+
+ if (originalEntity is ILoggable loggableOriginal)
+ {
+ return loggableOriginal.GetLog();
+ }
+
+ return null;
+ }
+
+ private async Task SaveLogsAsync()
+ {
+ if (_pendingLogs.Count == 0) return;
+ await auditService.LogAsync(_pendingLogs);
+ _pendingLogs.Clear();
+ }
+
+ private void SaveLogs()
+ {
+ if (_pendingLogs.Count == 0) return;
+ auditService.Log(_pendingLogs);
+ _pendingLogs.Clear();
+ }
+}
\ No newline at end of file
diff --git a/Fin.Infrastructure/Audits/Enums/AuditLogAction.cs b/Fin.Infrastructure/Audits/Enums/AuditLogAction.cs
new file mode 100644
index 0000000..fc9cff8
--- /dev/null
+++ b/Fin.Infrastructure/Audits/Enums/AuditLogAction.cs
@@ -0,0 +1,8 @@
+namespace Fin.Infrastructure.Audits.Enums;
+
+public enum AuditLogAction
+{
+ Created = 0,
+ Updated = 1,
+ Deleted = 2
+}
\ No newline at end of file
diff --git a/Fin.Infrastructure/Audits/Interfaces/IAuditLogService.cs b/Fin.Infrastructure/Audits/Interfaces/IAuditLogService.cs
new file mode 100644
index 0000000..e6daf75
--- /dev/null
+++ b/Fin.Infrastructure/Audits/Interfaces/IAuditLogService.cs
@@ -0,0 +1,7 @@
+namespace Fin.Infrastructure.Audits.Interfaces;
+
+public interface IAuditLogService
+{
+ Task LogAsync(List logs);
+ void Log(List logs);
+}
\ No newline at end of file
diff --git a/Fin.Infrastructure/Audits/MongoAuditLogService.cs b/Fin.Infrastructure/Audits/MongoAuditLogService.cs
new file mode 100644
index 0000000..da55bbd
--- /dev/null
+++ b/Fin.Infrastructure/Audits/MongoAuditLogService.cs
@@ -0,0 +1,64 @@
+using Fin.Infrastructure.Audits.Interfaces;
+using Fin.Infrastructure.DateTimes;
+using Microsoft.Extensions.Logging;
+using MongoDB.Driver;
+
+namespace Fin.Infrastructure.Audits;
+
+public class MongoAuditLogService(
+ IMongoDatabase database,
+ IDateTimeProvider dateTimeProvider,
+ ILogger logger
+ ) : IAuditLogService
+{
+ private readonly IMongoCollection _collection = database.GetCollection("audit_logs");
+
+ public async Task LogAsync(List logs)
+ {
+ try
+ {
+ var entitiesLog = logs.Select(log => new AuditLogDocument
+ {
+ EntityName = log.EntityName,
+ EntityId = log.EntityId,
+ NewValue = log.NewValue,
+ OldValue = log.OldValue,
+ Action = log.Action,
+ DateTime = dateTimeProvider.UtcNow(),
+ UserId = log.UserId,
+ TenantId = log.TenantId
+ });
+
+ await _collection.InsertManyAsync(entitiesLog);
+ }
+ catch (Exception ex)
+ {
+ logger.LogError("Error on saving log: {ExMessage}", ex.Message);
+ }
+ }
+
+ public void Log(List logs)
+ {
+ try
+ {
+
+ var entitiesLog = logs.Select(log => new AuditLogDocument
+ {
+ EntityName = log.EntityName,
+ EntityId = log.EntityId,
+ NewValue = log.NewValue,
+ OldValue = log.OldValue,
+ Action = log.Action,
+ DateTime = dateTimeProvider.UtcNow(),
+ UserId = log.UserId,
+ TenantId = log.TenantId
+ });
+
+ _collection.InsertMany(entitiesLog);
+ }
+ catch (Exception ex)
+ {
+ logger.LogError("Error on saving log: {ExMessage}", ex.Message);
+ }
+ }
+}
\ No newline at end of file
diff --git a/Fin.Infrastructure/Database/Extensions/AddDatabaseExtension.cs b/Fin.Infrastructure/Database/Extensions/AddDatabaseExtension.cs
index ecd6708..aaadffb 100644
--- a/Fin.Infrastructure/Database/Extensions/AddDatabaseExtension.cs
+++ b/Fin.Infrastructure/Database/Extensions/AddDatabaseExtension.cs
@@ -1,4 +1,5 @@
-using Fin.Infrastructure.Database.Interceptors;
+using Fin.Infrastructure.Audits;
+using Fin.Infrastructure.Database.Interceptors;
using Fin.Infrastructure.Database.Repositories;
using Microsoft.AspNetCore.Builder;
using Microsoft.EntityFrameworkCore;
@@ -19,11 +20,13 @@ public static IServiceCollection AddDatabase(this IServiceCollection services, I
{
var auditedEntityInterceptor = serviceProvider.GetRequiredService();
var tenantEntityInterceptor = serviceProvider.GetRequiredService();
+ var auditLogInterceptor = serviceProvider.GetRequiredService();
op
.UseNpgsql(configuration.GetConnectionString("DefaultConnection"))
.AddInterceptors(auditedEntityInterceptor)
- .AddInterceptors(tenantEntityInterceptor);
+ .AddInterceptors(tenantEntityInterceptor)
+ .AddInterceptors(auditLogInterceptor);
})
.AddScoped(typeof(IRepository<>), typeof(Repository<>));;
diff --git a/Fin.Infrastructure/Extensions/AddInfrastructureExtension.cs b/Fin.Infrastructure/Extensions/AddInfrastructureExtension.cs
index e95fb8c..b25320a 100644
--- a/Fin.Infrastructure/Extensions/AddInfrastructureExtension.cs
+++ b/Fin.Infrastructure/Extensions/AddInfrastructureExtension.cs
@@ -1,4 +1,5 @@
using Fin.Infrastructure.AmbientDatas;
+using Fin.Infrastructure.Audits;
using Fin.Infrastructure.Authentications;
using Fin.Infrastructure.AutoServices.Extensions;
using Fin.Infrastructure.BackgroundJobs;
@@ -26,6 +27,7 @@ public static IServiceCollection AddInfrastructure(this IServiceCollection servi
.AddScoped()
.AddScoped()
.AddScoped()
+ .AddAuditLog(configuration)
.AddDatabase(configuration)
.AddFirebase(configuration)
.AddSeeders()
diff --git a/Fin.Infrastructure/Fin.Infrastructure.csproj b/Fin.Infrastructure/Fin.Infrastructure.csproj
index 7d78e06..d10c34f 100644
--- a/Fin.Infrastructure/Fin.Infrastructure.csproj
+++ b/Fin.Infrastructure/Fin.Infrastructure.csproj
@@ -26,6 +26,7 @@
+
diff --git a/docker-compose.yml b/docker-compose.yml
index baa4cf6..4baca1f 100644
--- a/docker-compose.yml
+++ b/docker-compose.yml
@@ -32,6 +32,15 @@ services:
interval: 10s
timeout: 3s
retries: 5
+
+ mongodb:
+ image: mongo:latest
+ container_name: mongodb
+ restart: always
+ ports:
+ - "27017:27017"
+ volumes:
+ - mongo-data:/data/db
volumes:
postgres_data: