using System; using System.ComponentModel.DataAnnotations; using System.Threading; using System.Threading.Tasks; using FrameworkDAL.CustomCode.UserVsDashboard; using FrameworkDAL.DTO.Dashboard; using GB5Shared.DaprCache; using GB5Shared.DTO.Framework.AutoNumber; using GB5Shared.DTO.Framework.Login; using GB5Shared.GenerateAutoNumber; using GB5Shared.Resource.Response; using GB5Shared.Validation; using static GB5Shared.GB5Constant.Constant; namespace FrameworkBLL.UserVsDashboard { public class UserVsDashboardBLL : IUserVsDashboardBLL { private readonly IUserVsDashboardDAL _UserVsDashboardDAL; private readonly AutoNumber _AutoNumber; private readonly IValidation _Validation; private readonly KeyInvalidate _KeyInvalidate; public UserVsDashboardBLL(IUserVsDashboardDAL UserVsDashboardDAL, AutoNumber AutoNumber, IValidation Validation, KeyInvalidate KeyInvalidate) { _UserVsDashboardDAL = UserVsDashboardDAL; _AutoNumber = AutoNumber; _Validation = Validation; _KeyInvalidate = KeyInvalidate; } public async Task GetUserVsDashboard(int UserId, LoginDTO LoginDTO, CancellationToken ct = default) { try { return await _UserVsDashboardDAL.GetUserVsDashboard(UserId, LoginDTO, ct); } catch (Exception) { throw; } } public async Task SaveUserVsDashboard(UserVsDashboardDTO UserVsDashboardDTO, LoginDTO LoginDTO, CancellationToken ct = default) { if (UserVsDashboardDTO == null) throw new ArgumentNullException(nameof(UserVsDashboardDTO)); AutoNumberDTO? autoNumberDTO = null; try { string result; if (UserVsDashboardDTO.UserVsDashboardId == 0) { autoNumberDTO = await _AutoNumber.GetAutoNumber(1, "USERVSDASHBOARD", LoginDTO); UserVsDashboardDTO.UserVsDashboardId = autoNumberDTO.StartNumber; await _UserVsDashboardDAL.SaveUserVsDashboard(UserVsDashboardDTO, LoginDTO, ct); result = $"{SuccessResponse.SaveSuccessMessage} {UserVsDashboardDTO.UserVsDashboardId}"; } else { await _UserVsDashboardDAL.UpdateUserVsDashboard(UserVsDashboardDTO, LoginDTO, ct); result = $"{SuccessResponse.UpdateSuccessMessage} {UserVsDashboardDTO.UserVsDashboardId}"; } // Pin/reorder is a self-service action a user can repeat often (dragging to // reorder), so a full cache flush per write (as DashboardBLL uses for rarer // admin-driven Dashboard saves) would be wasteful — every OTHER user's Portlet/ // EntityViewer/Report caches would get nuked too. Precisely invalidate only the // two keys this write can actually affect: this user's own GetUserVsDashboard // list, and GetDashboardListForUser's key for (this user, their current role). // // Both USER_LEVEL keys embed LoginDTO.UserId in the hash, NOT the UserId being // acted on — so this is only correct when the caller IS that user (self-service). // If some future caller manages another user's pins on their behalf (LoginDTO. // UserId != UserVsDashboardDTO.UserId), fall back to the (slower but always // correct) full flush rather than silently computing the wrong, ineffective key. if (LoginDTO.UserId == UserVsDashboardDTO.UserId) { await _KeyInvalidate.InvalidateByKey( UserVsDashboardDTO.UserId, EntityConstant.OBJECTUSERVSDASHBOARD, CacheKeyLevel.USER_LEVEL, LoginDTO).ConfigureAwait(false); await _KeyInvalidate.InvalidateByKey( $"{UserVsDashboardDTO.UserId}_{LoginDTO.RoleId}", EntityConstant.OBJECTDASHBOARD, CacheKeyLevel.USER_LEVEL, LoginDTO).ConfigureAwait(false); } else { await _KeyInvalidate.AllInvalidateCache("-1").ConfigureAwait(false); } return result; } catch (ValidationException vex) { throw new Exception(vex.Message); } catch (Exception) { if (autoNumberDTO != null && UserVsDashboardDTO.UserVsDashboardId > 0) await _AutoNumber.RollbackAutoNumber("USERVSDASHBOARD", UserVsDashboardDTO.UserVsDashboardId, LoginDTO); throw; } } public async Task DeleteUserVsDashboard(int UserVsDashboardId, LoginDTO LoginDTO, CancellationToken ct = default) { try { var result = await _UserVsDashboardDAL.DeleteUserVsDashboard(UserVsDashboardId, LoginDTO, ct); // Only the surrogate id is known here — the affected UserId isn't, so the exact // GetUserVsDashboard/GetDashboardListForUser keys can't be reproduced without an // extra lookup. Full flush (less-used admin path; DeleteUserVsDashboardWithUser- // AndDashboard below is the self-service "unpin" path and invalidates precisely). await _KeyInvalidate.AllInvalidateCache("-1").ConfigureAwait(false); return result; } catch (Exception) { throw; } } public async Task DeleteUserVsDashboardWithUserAndDashboard(int UserId, int DashboardId, LoginDTO LoginDTO, CancellationToken ct = default) { try { var result = await _UserVsDashboardDAL.DeleteUserVsDashboardWithUserAndDashboard(UserId, DashboardId, LoginDTO, ct); // Same self-service assumption/guard as SaveUserVsDashboard above. if (LoginDTO.UserId == UserId) { await _KeyInvalidate.InvalidateByKey( UserId, EntityConstant.OBJECTUSERVSDASHBOARD, CacheKeyLevel.USER_LEVEL, LoginDTO).ConfigureAwait(false); await _KeyInvalidate.InvalidateByKey( $"{UserId}_{LoginDTO.RoleId}", EntityConstant.OBJECTDASHBOARD, CacheKeyLevel.USER_LEVEL, LoginDTO).ConfigureAwait(false); } else { await _KeyInvalidate.AllInvalidateCache("-1").ConfigureAwait(false); } return result; } catch (Exception) { throw; } } } }