using ECPBLL.Communication.Interfaces; using ECPDAL.Communication.DTOs; using ECPDAL.Communication.Interfaces; using GB5Shared.DaprCache; using GB5Shared.DTO.Framework.Login; using GB5Shared.EventLogPublish; using GB5Shared.GB5Constant; using GB5Shared.GenerateAutoNumber; using GB5Shared.Resource.Response; using GB5Shared.Telemetry; using Microsoft.Extensions.Logging; namespace ECPBLL.Communication.Implementations; public class CommentReactionBLL( ICommentReactionDAL _dal, ICommentThreadDAL _threadDal, ICommentThreadBLL _threadBll, AutoNumber _autoNumber, KeyInvalidate _keyInvalidate, EventLogPublish _eventLog, ICommunicationPushService _pushService, ILogger _logger ) : ICommentReactionBLL { public async Task SaveCommentReactionAsync(SaveCommentReactionDTO dto, LoginDTO login, CancellationToken ct) { GB5Trace.Step("validate-comment-reaction", new { dto.CommentThreadId, dto.ReactionType }); if (dto.ReactionType is < 1 or > 4) throw new InvalidOperationException("ReactionType must be 1 (like), 2 (love), 3 (laugh), or 4 (insightful)."); try { var thread = await _threadDal.GetCommentThreadAsync(dto.CommentThreadId, login, ct).ConfigureAwait(false); if (thread is null) throw new InvalidOperationException($"Comment thread {dto.CommentThreadId} not found."); if (!await _threadBll.CanViewCommentThreadAsync(thread, login, ct).ConfigureAwait(false)) throw new UnauthorizedAccessException("You do not have access to this comment thread."); GB5Trace.Step("check-existing-reaction", new { dto.CommentThreadId, dto.ReactionType, login.UserId }); var existing = await _dal.GetUserReactionAsync(dto.CommentThreadId, login.UserId, dto.ReactionType, login, ct) .ConfigureAwait(false); if (existing is not null) return SuccessResponse.SaveSuccess; var autoNumber = await _autoNumber.GetNumberAsync(1, "COMMENTREACTION", login).ConfigureAwait(false); var entity = new CommentReactionDTO { CommentReactionId = autoNumber.StartNumber, CommentThreadId = dto.CommentThreadId, UserId = login.UserId, UserCode = login.UserCode, UserName = login.UserName, ReactionType = dto.ReactionType, TenantId = login.ClientId, DatabaseName = login.DatabaseName, DatabaseType = login.DatabaseType, Status = CommunicationConstants.RowStatus.Active, CreatedById = login.UserId, CreatedOn = DateTime.UtcNow, ModifiedById = login.UserId, ModifiedOn = DateTime.UtcNow, }; GB5Trace.Step("save-comment-reaction", new { entity.CommentReactionId }); await _dal.SaveCommentReactionAsync(entity, login, ct).ConfigureAwait(false); GB5Trace.Step("event-publish", new { Constant.EventTypeConstant.COMMENTREACTEDEVENTTYPEID }); await _eventLog.PublishEventLogAsync( "Comment Reacted", entity, Constant.EventTypeConstant.COMMENTREACTEDEVENTTYPEID, entity.CommentReactionId, login, ct: ct) .ConfigureAwait(false); await InvalidateThreadCacheAsync(thread.ObjectTypeId, thread.ObjectId, login).ConfigureAwait(false); await _pushService.PushReactionAsync(thread.ObjectTypeId, thread.ObjectId, entity, ct).ConfigureAwait(false); return SuccessResponse.SaveSuccess; } catch (Exception ex) { GB5Trace.MarkFailed("save-comment-reaction-failed", ex); _logger.LogError(ex, "SaveCommentReaction failed for CommentThreadId {CommentThreadId}", dto.CommentThreadId); throw; } } public async Task DeleteCommentReactionAsync(int commentThreadId, byte reactionType, LoginDTO login, CancellationToken ct) { GB5Trace.Step("delete-comment-reaction", new { commentThreadId, reactionType }); try { var thread = await _threadDal.GetCommentThreadAsync(commentThreadId, login, ct).ConfigureAwait(false); if (thread is null) throw new InvalidOperationException($"Comment thread {commentThreadId} not found."); await _dal.DeleteCommentReactionAsync(commentThreadId, login.UserId, reactionType, login, ct) .ConfigureAwait(false); GB5Trace.Step("event-publish", new { Constant.EventTypeConstant.COMMENTREACTIONREMOVEDEVENTTYPEID }); await _eventLog.PublishEventLogAsync( "Comment Reaction Removed", new { commentThreadId, reactionType, login.UserId }, Constant.EventTypeConstant.COMMENTREACTIONREMOVEDEVENTTYPEID, commentThreadId, login, ct: ct) .ConfigureAwait(false); await InvalidateThreadCacheAsync(thread.ObjectTypeId, thread.ObjectId, login).ConfigureAwait(false); return SuccessResponse.DeleteSuccessMessage; } catch (Exception ex) { GB5Trace.MarkFailed("delete-comment-reaction-failed", ex); _logger.LogError(ex, "DeleteCommentReaction failed for CommentThreadId {CommentThreadId}", commentThreadId); throw; } } private async Task InvalidateThreadCacheAsync(int objectTypeId, int objectId, LoginDTO login) { var cacheKey = new CacheKeyGeneration().KeyGeneration( $"{objectTypeId}:{objectId}", Constant.EntityConstant.OBJECTCOMMENTTHREADLIST, Constant.CacheKeyLevel.CLIENT_LEVEL, login); await _keyInvalidate.InvalidateCache(cacheKey).ConfigureAwait(false); } }