Messenger Tüm Mesajlar Logluyor
This commit is contained in:
parent
7632c9e8a0
commit
af726c530a
17 changed files with 1196 additions and 92 deletions
|
|
@ -3,6 +3,7 @@
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.ComponentModel.DataAnnotations;
|
using System.ComponentModel.DataAnnotations;
|
||||||
|
using Volo.Abp.Application.Dtos;
|
||||||
using Volo.Abp.Content;
|
using Volo.Abp.Content;
|
||||||
|
|
||||||
namespace Sozsoft.Platform.Messenger;
|
namespace Sozsoft.Platform.Messenger;
|
||||||
|
|
@ -36,6 +37,8 @@ public class MessengerUploadAttachmentInput
|
||||||
|
|
||||||
public class MessengerSendMessageDto
|
public class MessengerSendMessageDto
|
||||||
{
|
{
|
||||||
|
public Guid? ConversationId { get; set; }
|
||||||
|
|
||||||
[Required]
|
[Required]
|
||||||
public List<Guid> RecipientIds { get; set; } = new();
|
public List<Guid> RecipientIds { get; set; } = new();
|
||||||
|
|
||||||
|
|
@ -49,6 +52,7 @@ public class MessengerMessageDto
|
||||||
{
|
{
|
||||||
public Guid Id { get; set; }
|
public Guid Id { get; set; }
|
||||||
public Guid? TenantId { get; set; }
|
public Guid? TenantId { get; set; }
|
||||||
|
public Guid ConversationId { get; set; }
|
||||||
public Guid SenderId { get; set; }
|
public Guid SenderId { get; set; }
|
||||||
public string SenderUserName { get; set; } = string.Empty;
|
public string SenderUserName { get; set; } = string.Empty;
|
||||||
public string SenderName { get; set; } = string.Empty;
|
public string SenderName { get; set; } = string.Empty;
|
||||||
|
|
@ -57,3 +61,43 @@ public class MessengerMessageDto
|
||||||
public List<MessengerAttachmentDto> Attachments { get; set; } = new();
|
public List<MessengerAttachmentDto> Attachments { get; set; } = new();
|
||||||
public DateTime SentAt { get; set; }
|
public DateTime SentAt { get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public class MessengerMessageDeletedDto
|
||||||
|
{
|
||||||
|
public Guid MessageId { get; set; }
|
||||||
|
public Guid ConversationId { get; set; }
|
||||||
|
public Guid SenderId { get; set; }
|
||||||
|
public List<Guid> RecipientIds { get; set; } = new();
|
||||||
|
}
|
||||||
|
|
||||||
|
public class MessengerConversationDto : FullAuditedEntityDto<Guid>
|
||||||
|
{
|
||||||
|
public Guid? TenantId { get; set; }
|
||||||
|
public string? Title { get; set; }
|
||||||
|
public List<Guid> ParticipantIds { get; set; } = new();
|
||||||
|
public bool IsGroup { get; set; }
|
||||||
|
public Guid? LastSenderId { get; set; }
|
||||||
|
public string? LastMessagePreview { get; set; }
|
||||||
|
public DateTime? LastMessageTime { get; set; }
|
||||||
|
public int MessageCount { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public class MessengerConversationCreateUpdateDto
|
||||||
|
{
|
||||||
|
[StringLength(256)]
|
||||||
|
public string? Title { get; set; }
|
||||||
|
|
||||||
|
[Required]
|
||||||
|
public List<Guid> ParticipantIds { get; set; } = new();
|
||||||
|
}
|
||||||
|
|
||||||
|
public class MessengerConversationListRequestDto : PagedAndSortedResultRequestDto
|
||||||
|
{
|
||||||
|
public string? Filter { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public class MessengerGetMessagesInput : PagedAndSortedResultRequestDto
|
||||||
|
{
|
||||||
|
public Guid? ConversationId { get; set; }
|
||||||
|
public List<Guid> ParticipantIds { get; set; } = new();
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -4,12 +4,16 @@ using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using System.Linq.Dynamic.Core;
|
||||||
|
using System.Text.Json;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Sozsoft.Platform.BlobStoring;
|
using Sozsoft.Platform.BlobStoring;
|
||||||
|
using Sozsoft.Platform.Entities;
|
||||||
using Microsoft.AspNetCore.Authorization;
|
using Microsoft.AspNetCore.Authorization;
|
||||||
using Microsoft.AspNetCore.Mvc;
|
using Microsoft.AspNetCore.Mvc;
|
||||||
using Microsoft.Extensions.Configuration;
|
using Microsoft.Extensions.Configuration;
|
||||||
using Volo.Abp;
|
using Volo.Abp;
|
||||||
|
using Volo.Abp.Application.Dtos;
|
||||||
using Volo.Abp.Application.Services;
|
using Volo.Abp.Application.Services;
|
||||||
using Volo.Abp.Domain.Repositories;
|
using Volo.Abp.Domain.Repositories;
|
||||||
using Volo.Abp.Identity;
|
using Volo.Abp.Identity;
|
||||||
|
|
@ -20,15 +24,21 @@ namespace Sozsoft.Platform.Messenger;
|
||||||
public class MessengerAppService : ApplicationService
|
public class MessengerAppService : ApplicationService
|
||||||
{
|
{
|
||||||
private readonly IRepository<IdentityUser, Guid> _userRepository;
|
private readonly IRepository<IdentityUser, Guid> _userRepository;
|
||||||
|
private readonly IRepository<MessengerConversation, Guid> _conversationRepository;
|
||||||
|
private readonly IRepository<MessengerConversationMessage, Guid> _messageRepository;
|
||||||
private readonly BlobManager _blobManager;
|
private readonly BlobManager _blobManager;
|
||||||
private readonly IConfiguration _configuration;
|
private readonly IConfiguration _configuration;
|
||||||
|
|
||||||
public MessengerAppService(
|
public MessengerAppService(
|
||||||
IRepository<IdentityUser, Guid> userRepository,
|
IRepository<IdentityUser, Guid> userRepository,
|
||||||
|
IRepository<MessengerConversation, Guid> conversationRepository,
|
||||||
|
IRepository<MessengerConversationMessage, Guid> messageRepository,
|
||||||
BlobManager blobManager,
|
BlobManager blobManager,
|
||||||
IConfiguration configuration)
|
IConfiguration configuration)
|
||||||
{
|
{
|
||||||
_userRepository = userRepository;
|
_userRepository = userRepository;
|
||||||
|
_conversationRepository = conversationRepository;
|
||||||
|
_messageRepository = messageRepository;
|
||||||
_blobManager = blobManager;
|
_blobManager = blobManager;
|
||||||
_configuration = configuration;
|
_configuration = configuration;
|
||||||
}
|
}
|
||||||
|
|
@ -78,6 +88,250 @@ public class MessengerAppService : ApplicationService
|
||||||
}).ToList();
|
}).ToList();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async Task<PagedResultDto<MessengerConversationDto>> GetConversationsAsync(MessengerConversationListRequestDto input)
|
||||||
|
{
|
||||||
|
var query = await _conversationRepository.GetQueryableAsync();
|
||||||
|
var currentUserId = GetCurrentUserId();
|
||||||
|
var currentUserKey = currentUserId.ToString("N");
|
||||||
|
|
||||||
|
query = query.Where(conversation => conversation.ParticipantKey.Contains(currentUserKey));
|
||||||
|
|
||||||
|
if (!input.Filter.IsNullOrWhiteSpace())
|
||||||
|
{
|
||||||
|
var filter = input.Filter!.Trim().ToLower();
|
||||||
|
query = query.Where(conversation =>
|
||||||
|
(conversation.Title != null && conversation.Title.ToLower().Contains(filter)) ||
|
||||||
|
(conversation.LastMessagePreview != null && conversation.LastMessagePreview.ToLower().Contains(filter)));
|
||||||
|
}
|
||||||
|
|
||||||
|
var totalCount = await AsyncExecuter.CountAsync(query);
|
||||||
|
var sorting = input.Sorting.IsNullOrWhiteSpace()
|
||||||
|
? $"{nameof(MessengerConversation.LastMessageTime)} desc"
|
||||||
|
: input.Sorting;
|
||||||
|
|
||||||
|
var conversations = await AsyncExecuter.ToListAsync(
|
||||||
|
query.OrderBy(sorting).Skip(input.SkipCount).Take(input.MaxResultCount)
|
||||||
|
);
|
||||||
|
|
||||||
|
return new PagedResultDto<MessengerConversationDto>(
|
||||||
|
totalCount,
|
||||||
|
conversations.Select(MapConversation).ToList()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<MessengerConversationDto> GetConversationAsync(Guid id)
|
||||||
|
{
|
||||||
|
var conversation = await _conversationRepository.GetAsync(id);
|
||||||
|
EnsureConversationParticipant(conversation, GetCurrentUserId());
|
||||||
|
|
||||||
|
return MapConversation(conversation);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<MessengerConversationDto> CreateConversationAsync(MessengerConversationCreateUpdateDto input)
|
||||||
|
{
|
||||||
|
var currentUserId = GetCurrentUserId();
|
||||||
|
var participantIds = await NormalizeAndValidateParticipantIdsAsync(input.ParticipantIds, currentUserId);
|
||||||
|
var participantKey = CreateParticipantKey(participantIds);
|
||||||
|
|
||||||
|
var existing = await FindConversationByParticipantKeyAsync(participantKey);
|
||||||
|
if (existing is not null)
|
||||||
|
{
|
||||||
|
return MapConversation(existing);
|
||||||
|
}
|
||||||
|
|
||||||
|
var conversation = new MessengerConversation(GuidGenerator.Create())
|
||||||
|
{
|
||||||
|
TenantId = CurrentTenant.Id,
|
||||||
|
Title = input.Title?.Trim(),
|
||||||
|
ParticipantKey = participantKey,
|
||||||
|
ParticipantIdsJson = JsonSerializer.Serialize(participantIds),
|
||||||
|
IsGroup = participantIds.Count > 2
|
||||||
|
};
|
||||||
|
|
||||||
|
await _conversationRepository.InsertAsync(conversation, autoSave: true);
|
||||||
|
|
||||||
|
return MapConversation(conversation);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<MessengerConversationDto> UpdateConversationAsync(Guid id, MessengerConversationCreateUpdateDto input)
|
||||||
|
{
|
||||||
|
var conversation = await _conversationRepository.GetAsync(id);
|
||||||
|
var currentUserId = GetCurrentUserId();
|
||||||
|
EnsureConversationParticipant(conversation, currentUserId);
|
||||||
|
|
||||||
|
var participantIds = await NormalizeAndValidateParticipantIdsAsync(input.ParticipantIds, currentUserId);
|
||||||
|
var participantKey = CreateParticipantKey(participantIds);
|
||||||
|
|
||||||
|
conversation.Title = input.Title?.Trim();
|
||||||
|
conversation.ParticipantKey = participantKey;
|
||||||
|
conversation.ParticipantIdsJson = JsonSerializer.Serialize(participantIds);
|
||||||
|
conversation.IsGroup = participantIds.Count > 2;
|
||||||
|
|
||||||
|
await _conversationRepository.UpdateAsync(conversation, autoSave: true);
|
||||||
|
|
||||||
|
return MapConversation(conversation);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task DeleteConversationAsync(Guid id)
|
||||||
|
{
|
||||||
|
var conversation = await _conversationRepository.GetAsync(id);
|
||||||
|
EnsureConversationParticipant(conversation, GetCurrentUserId());
|
||||||
|
|
||||||
|
await _conversationRepository.DeleteAsync(conversation, autoSave: true);
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpPost("api/app/messenger/messages")]
|
||||||
|
public async Task<List<MessengerMessageDto>> GetMessagesAsync(MessengerGetMessagesInput input)
|
||||||
|
{
|
||||||
|
var currentUserId = GetCurrentUserId();
|
||||||
|
var conversation = input.ConversationId.HasValue
|
||||||
|
? await _conversationRepository.GetAsync(input.ConversationId.Value)
|
||||||
|
: await FindConversationByParticipantKeyAsync(
|
||||||
|
CreateParticipantKey(await NormalizeAndValidateParticipantIdsAsync(input.ParticipantIds, currentUserId))
|
||||||
|
);
|
||||||
|
|
||||||
|
if (conversation is null)
|
||||||
|
{
|
||||||
|
return new List<MessengerMessageDto>();
|
||||||
|
}
|
||||||
|
|
||||||
|
EnsureConversationParticipant(conversation, currentUserId);
|
||||||
|
|
||||||
|
var query = await _messageRepository.GetQueryableAsync();
|
||||||
|
query = query.Where(message => message.ConversationId == conversation.Id);
|
||||||
|
|
||||||
|
var sorting = input.Sorting.IsNullOrWhiteSpace()
|
||||||
|
? $"{nameof(MessengerConversationMessage.SentAt)} desc"
|
||||||
|
: input.Sorting;
|
||||||
|
|
||||||
|
var messages = await AsyncExecuter.ToListAsync(
|
||||||
|
query.OrderBy(sorting).Skip(input.SkipCount).Take(input.MaxResultCount)
|
||||||
|
);
|
||||||
|
|
||||||
|
return messages
|
||||||
|
.OrderBy(message => message.SentAt)
|
||||||
|
.Select(MapMessage)
|
||||||
|
.ToList();
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<MessengerMessageDto> SendMessageAsync(MessengerSendMessageDto input)
|
||||||
|
{
|
||||||
|
var senderId = GetCurrentUserId();
|
||||||
|
var recipientIds = input.RecipientIds
|
||||||
|
.Where(id => id != Guid.Empty && id != senderId)
|
||||||
|
.Distinct()
|
||||||
|
.ToList();
|
||||||
|
|
||||||
|
if (recipientIds.Count == 0)
|
||||||
|
{
|
||||||
|
throw new UserFriendlyException("En az bir alici secilmelidir.");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (input.Text.IsNullOrWhiteSpace() && input.Attachments.Count == 0)
|
||||||
|
{
|
||||||
|
throw new UserFriendlyException("Mesaj veya dosya gonderilmelidir.");
|
||||||
|
}
|
||||||
|
|
||||||
|
var validRecipients = await GetValidUsersAsync(recipientIds);
|
||||||
|
if (validRecipients.Count != recipientIds.Count)
|
||||||
|
{
|
||||||
|
throw new UserFriendlyException("Bu tenant icinde gecersiz alici var.");
|
||||||
|
}
|
||||||
|
|
||||||
|
MessengerConversation? conversation = null;
|
||||||
|
if (input.ConversationId.HasValue)
|
||||||
|
{
|
||||||
|
conversation = await _conversationRepository.GetAsync(input.ConversationId.Value);
|
||||||
|
EnsureConversationParticipant(conversation, senderId);
|
||||||
|
}
|
||||||
|
|
||||||
|
var participantIds = recipientIds.Append(senderId).Distinct().ToList();
|
||||||
|
var participantKey = CreateParticipantKey(participantIds);
|
||||||
|
conversation ??= await FindConversationByParticipantKeyAsync(participantKey);
|
||||||
|
|
||||||
|
if (conversation is null)
|
||||||
|
{
|
||||||
|
conversation = new MessengerConversation(GuidGenerator.Create())
|
||||||
|
{
|
||||||
|
TenantId = CurrentTenant.Id,
|
||||||
|
ParticipantKey = participantKey,
|
||||||
|
ParticipantIdsJson = JsonSerializer.Serialize(participantIds),
|
||||||
|
IsGroup = participantIds.Count > 2
|
||||||
|
};
|
||||||
|
|
||||||
|
await _conversationRepository.InsertAsync(conversation, autoSave: true);
|
||||||
|
}
|
||||||
|
|
||||||
|
var senderName = $"{CurrentUser.Name} {CurrentUser.SurName}".Trim() ?? CurrentUser.UserName ?? "Kullanici";
|
||||||
|
var trimmedText = input.Text?.Trim();
|
||||||
|
var message = new MessengerConversationMessage(GuidGenerator.Create())
|
||||||
|
{
|
||||||
|
TenantId = CurrentTenant.Id,
|
||||||
|
ConversationId = conversation.Id,
|
||||||
|
SenderId = senderId,
|
||||||
|
SenderUserName = CurrentUser.UserName ?? string.Empty,
|
||||||
|
SenderName = senderName,
|
||||||
|
RecipientIdsJson = JsonSerializer.Serialize(recipientIds),
|
||||||
|
Text = trimmedText,
|
||||||
|
AttachmentsJson = JsonSerializer.Serialize(input.Attachments),
|
||||||
|
SentAt = Clock.Now
|
||||||
|
};
|
||||||
|
|
||||||
|
await _messageRepository.InsertAsync(message, autoSave: true);
|
||||||
|
|
||||||
|
conversation.LastSenderId = senderId;
|
||||||
|
conversation.LastMessagePreview = GetMessagePreview(trimmedText, input.Attachments.Count);
|
||||||
|
conversation.LastMessageTime = message.SentAt;
|
||||||
|
conversation.MessageCount += 1;
|
||||||
|
await _conversationRepository.UpdateAsync(conversation, autoSave: true);
|
||||||
|
|
||||||
|
return MapMessage(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<MessengerMessageDeletedDto> DeleteMessageAsync(Guid id)
|
||||||
|
{
|
||||||
|
var currentUserId = GetCurrentUserId();
|
||||||
|
var message = await _messageRepository.GetAsync(id);
|
||||||
|
|
||||||
|
if (message.SenderId != currentUserId)
|
||||||
|
{
|
||||||
|
throw new UserFriendlyException("Sadece kendi mesajlarinizi silebilirsiniz.");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Clock.Now - message.SentAt > TimeSpan.FromMinutes(10))
|
||||||
|
{
|
||||||
|
throw new UserFriendlyException("Mesajlar ilk 10 dakika icinde silinebilir.");
|
||||||
|
}
|
||||||
|
|
||||||
|
var recipientIds = DeserializeGuidList(message.RecipientIdsJson);
|
||||||
|
var conversation = await _conversationRepository.GetAsync(message.ConversationId);
|
||||||
|
|
||||||
|
await _messageRepository.DeleteAsync(message, autoSave: true);
|
||||||
|
|
||||||
|
var query = await _messageRepository.GetQueryableAsync();
|
||||||
|
var lastMessage = await AsyncExecuter.FirstOrDefaultAsync(
|
||||||
|
query
|
||||||
|
.Where(item => item.ConversationId == conversation.Id)
|
||||||
|
.OrderByDescending(item => item.SentAt)
|
||||||
|
);
|
||||||
|
|
||||||
|
conversation.MessageCount = Math.Max(0, conversation.MessageCount - 1);
|
||||||
|
conversation.LastSenderId = lastMessage?.SenderId;
|
||||||
|
conversation.LastMessagePreview = lastMessage is null
|
||||||
|
? null
|
||||||
|
: GetMessagePreview(lastMessage.Text, DeserializeAttachments(lastMessage.AttachmentsJson).Count);
|
||||||
|
conversation.LastMessageTime = lastMessage?.SentAt;
|
||||||
|
await _conversationRepository.UpdateAsync(conversation, autoSave: true);
|
||||||
|
|
||||||
|
return new MessengerMessageDeletedDto
|
||||||
|
{
|
||||||
|
MessageId = message.Id,
|
||||||
|
ConversationId = message.ConversationId,
|
||||||
|
SenderId = message.SenderId,
|
||||||
|
RecipientIds = recipientIds
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
[HttpPost("api/app/messenger/upload-attachment")]
|
[HttpPost("api/app/messenger/upload-attachment")]
|
||||||
public async Task<MessengerAttachmentDto> UploadAttachmentAsync([FromForm] MessengerUploadAttachmentInput input)
|
public async Task<MessengerAttachmentDto> UploadAttachmentAsync([FromForm] MessengerUploadAttachmentInput input)
|
||||||
{
|
{
|
||||||
|
|
@ -111,4 +365,131 @@ public class MessengerAppService : ApplicationService
|
||||||
Url = $"{baseUrl}/{tenantPart}/{BlobContainerNames.Messenger}/{savedFileName}"
|
Url = $"{baseUrl}/{tenantPart}/{BlobContainerNames.Messenger}/{savedFileName}"
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private Guid GetCurrentUserId()
|
||||||
|
{
|
||||||
|
if (!CurrentUser.Id.HasValue)
|
||||||
|
{
|
||||||
|
throw new UserFriendlyException("Kullanici oturumu bulunamadi.");
|
||||||
|
}
|
||||||
|
|
||||||
|
return CurrentUser.Id.Value;
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task<List<Guid>> NormalizeAndValidateParticipantIdsAsync(IEnumerable<Guid> participantIds, Guid currentUserId)
|
||||||
|
{
|
||||||
|
var normalizedIds = participantIds
|
||||||
|
.Where(id => id != Guid.Empty)
|
||||||
|
.Append(currentUserId)
|
||||||
|
.Distinct()
|
||||||
|
.ToList();
|
||||||
|
|
||||||
|
if (normalizedIds.Count < 2)
|
||||||
|
{
|
||||||
|
throw new UserFriendlyException("En az iki katilimci secilmelidir.");
|
||||||
|
}
|
||||||
|
|
||||||
|
var users = await GetValidUsersAsync(normalizedIds);
|
||||||
|
if (users.Count != normalizedIds.Count)
|
||||||
|
{
|
||||||
|
throw new UserFriendlyException("Bu tenant icinde gecersiz katilimci var.");
|
||||||
|
}
|
||||||
|
|
||||||
|
return normalizedIds;
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task<List<IdentityUser>> GetValidUsersAsync(List<Guid> userIds)
|
||||||
|
{
|
||||||
|
return await _userRepository.GetListAsync(user =>
|
||||||
|
userIds.Contains(user.Id) &&
|
||||||
|
user.TenantId == CurrentTenant.Id &&
|
||||||
|
user.IsActive);
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task<MessengerConversation?> FindConversationByParticipantKeyAsync(string participantKey)
|
||||||
|
{
|
||||||
|
var query = await _conversationRepository.GetQueryableAsync();
|
||||||
|
return await AsyncExecuter.FirstOrDefaultAsync(query.Where(conversation => conversation.ParticipantKey == participantKey));
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string CreateParticipantKey(IEnumerable<Guid> participantIds)
|
||||||
|
{
|
||||||
|
return string.Join(",", participantIds.Select(id => id.ToString("N")).OrderBy(id => id));
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void EnsureConversationParticipant(MessengerConversation conversation, Guid currentUserId)
|
||||||
|
{
|
||||||
|
var participantIds = DeserializeGuidList(conversation.ParticipantIdsJson);
|
||||||
|
if (!participantIds.Contains(currentUserId))
|
||||||
|
{
|
||||||
|
throw new UserFriendlyException("Bu gorusmeye erisim yetkiniz yok.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static MessengerConversationDto MapConversation(MessengerConversation conversation)
|
||||||
|
{
|
||||||
|
return new MessengerConversationDto
|
||||||
|
{
|
||||||
|
Id = conversation.Id,
|
||||||
|
TenantId = conversation.TenantId,
|
||||||
|
Title = conversation.Title,
|
||||||
|
ParticipantIds = DeserializeGuidList(conversation.ParticipantIdsJson),
|
||||||
|
IsGroup = conversation.IsGroup,
|
||||||
|
LastSenderId = conversation.LastSenderId,
|
||||||
|
LastMessagePreview = conversation.LastMessagePreview,
|
||||||
|
LastMessageTime = conversation.LastMessageTime,
|
||||||
|
MessageCount = conversation.MessageCount,
|
||||||
|
CreationTime = conversation.CreationTime,
|
||||||
|
CreatorId = conversation.CreatorId,
|
||||||
|
LastModificationTime = conversation.LastModificationTime,
|
||||||
|
LastModifierId = conversation.LastModifierId
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private static MessengerMessageDto MapMessage(MessengerConversationMessage message)
|
||||||
|
{
|
||||||
|
return new MessengerMessageDto
|
||||||
|
{
|
||||||
|
Id = message.Id,
|
||||||
|
TenantId = message.TenantId,
|
||||||
|
ConversationId = message.ConversationId,
|
||||||
|
SenderId = message.SenderId,
|
||||||
|
SenderUserName = message.SenderUserName,
|
||||||
|
SenderName = message.SenderName,
|
||||||
|
RecipientIds = DeserializeGuidList(message.RecipientIdsJson),
|
||||||
|
Text = message.Text,
|
||||||
|
Attachments = DeserializeAttachments(message.AttachmentsJson),
|
||||||
|
SentAt = message.SentAt
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private static List<Guid> DeserializeGuidList(string? json)
|
||||||
|
{
|
||||||
|
if (json.IsNullOrWhiteSpace())
|
||||||
|
{
|
||||||
|
return new List<Guid>();
|
||||||
|
}
|
||||||
|
|
||||||
|
return JsonSerializer.Deserialize<List<Guid>>(json!) ?? new List<Guid>();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static List<MessengerAttachmentDto> DeserializeAttachments(string? json)
|
||||||
|
{
|
||||||
|
if (json.IsNullOrWhiteSpace())
|
||||||
|
{
|
||||||
|
return new List<MessengerAttachmentDto>();
|
||||||
|
}
|
||||||
|
|
||||||
|
return JsonSerializer.Deserialize<List<MessengerAttachmentDto>>(json!) ?? new List<MessengerAttachmentDto>();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string GetMessagePreview(string? text, int attachmentCount)
|
||||||
|
{
|
||||||
|
if (!text.IsNullOrWhiteSpace())
|
||||||
|
{
|
||||||
|
return text!.Length > 512 ? text[..512] : text;
|
||||||
|
}
|
||||||
|
|
||||||
|
return attachmentCount > 0 ? $"{attachmentCount} dosya" : string.Empty;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -5,9 +5,6 @@ using System.Linq;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Microsoft.AspNetCore.Authorization;
|
using Microsoft.AspNetCore.Authorization;
|
||||||
using Microsoft.AspNetCore.SignalR;
|
using Microsoft.AspNetCore.SignalR;
|
||||||
using Volo.Abp.Domain.Repositories;
|
|
||||||
using Volo.Abp.Guids;
|
|
||||||
using Volo.Abp.Identity;
|
|
||||||
using Volo.Abp.MultiTenancy;
|
using Volo.Abp.MultiTenancy;
|
||||||
using Volo.Abp.Users;
|
using Volo.Abp.Users;
|
||||||
|
|
||||||
|
|
@ -18,19 +15,16 @@ public class MessengerHub : Hub
|
||||||
{
|
{
|
||||||
private readonly ICurrentTenant _currentTenant;
|
private readonly ICurrentTenant _currentTenant;
|
||||||
private readonly ICurrentUser _currentUser;
|
private readonly ICurrentUser _currentUser;
|
||||||
private readonly IGuidGenerator _guidGenerator;
|
private readonly MessengerAppService _messengerAppService;
|
||||||
private readonly IRepository<IdentityUser, Guid> _userRepository;
|
|
||||||
|
|
||||||
public MessengerHub(
|
public MessengerHub(
|
||||||
ICurrentTenant currentTenant,
|
ICurrentTenant currentTenant,
|
||||||
ICurrentUser currentUser,
|
ICurrentUser currentUser,
|
||||||
IGuidGenerator guidGenerator,
|
MessengerAppService messengerAppService)
|
||||||
IRepository<IdentityUser, Guid> userRepository)
|
|
||||||
{
|
{
|
||||||
_currentTenant = currentTenant;
|
_currentTenant = currentTenant;
|
||||||
_currentUser = currentUser;
|
_currentUser = currentUser;
|
||||||
_guidGenerator = guidGenerator;
|
_messengerAppService = messengerAppService;
|
||||||
_userRepository = userRepository;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public override async Task OnConnectedAsync()
|
public override async Task OnConnectedAsync()
|
||||||
|
|
@ -57,50 +51,7 @@ public class MessengerHub : Hub
|
||||||
|
|
||||||
public async Task SendMessage(MessengerSendMessageDto input)
|
public async Task SendMessage(MessengerSendMessageDto input)
|
||||||
{
|
{
|
||||||
if (!_currentUser.Id.HasValue)
|
var message = await _messengerAppService.SendMessageAsync(input);
|
||||||
{
|
|
||||||
throw new HubException("Kullanıcı oturumu bulunamadı.");
|
|
||||||
}
|
|
||||||
|
|
||||||
var recipientIds = input.RecipientIds
|
|
||||||
.Where(id => id != Guid.Empty)
|
|
||||||
.Distinct()
|
|
||||||
.ToList();
|
|
||||||
|
|
||||||
if (recipientIds.Count == 0)
|
|
||||||
{
|
|
||||||
throw new HubException("En az bir alıcı seçilmelidir.");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (input.Text.IsNullOrWhiteSpace() && input.Attachments.Count == 0)
|
|
||||||
{
|
|
||||||
throw new HubException("Mesaj veya dosya gönderilmelidir.");
|
|
||||||
}
|
|
||||||
|
|
||||||
var tenantId = _currentTenant.Id;
|
|
||||||
var validRecipients = await _userRepository.GetListAsync(user =>
|
|
||||||
recipientIds.Contains(user.Id) &&
|
|
||||||
user.TenantId == tenantId &&
|
|
||||||
user.IsActive);
|
|
||||||
|
|
||||||
if (validRecipients.Count == 0)
|
|
||||||
{
|
|
||||||
throw new HubException("Bu tenant içinde geçerli alıcı bulunamadı.");
|
|
||||||
}
|
|
||||||
|
|
||||||
var senderName = _currentUser.Name ?? _currentUser.UserName ?? "Kullanıcı";
|
|
||||||
var message = new MessengerMessageDto
|
|
||||||
{
|
|
||||||
Id = _guidGenerator.Create(),
|
|
||||||
TenantId = tenantId,
|
|
||||||
SenderId = _currentUser.Id.Value,
|
|
||||||
SenderUserName = _currentUser.UserName ?? string.Empty,
|
|
||||||
SenderName = senderName,
|
|
||||||
RecipientIds = validRecipients.Select(user => user.Id).ToList(),
|
|
||||||
Text = input.Text?.Trim(),
|
|
||||||
Attachments = input.Attachments,
|
|
||||||
SentAt = DateTime.UtcNow
|
|
||||||
};
|
|
||||||
|
|
||||||
var targetGroups = message.RecipientIds
|
var targetGroups = message.RecipientIds
|
||||||
.Append(message.SenderId)
|
.Append(message.SenderId)
|
||||||
|
|
@ -111,6 +62,19 @@ public class MessengerHub : Hub
|
||||||
await Clients.Groups(targetGroups).SendAsync("MessengerMessageReceived", message);
|
await Clients.Groups(targetGroups).SendAsync("MessengerMessageReceived", message);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async Task DeleteMessage(Guid messageId)
|
||||||
|
{
|
||||||
|
var deletedMessage = await _messengerAppService.DeleteMessageAsync(messageId);
|
||||||
|
|
||||||
|
var targetGroups = deletedMessage.RecipientIds
|
||||||
|
.Append(deletedMessage.SenderId)
|
||||||
|
.Distinct()
|
||||||
|
.Select(UserGroupName)
|
||||||
|
.ToList();
|
||||||
|
|
||||||
|
await Clients.Groups(targetGroups).SendAsync("MessengerMessageDeleted", deletedMessage);
|
||||||
|
}
|
||||||
|
|
||||||
private string TenantKey => _currentTenant.Id?.ToString("N") ?? "host";
|
private string TenantKey => _currentTenant.Id?.ToString("N") ?? "host";
|
||||||
|
|
||||||
private string TenantGroupName() => $"messenger:tenant:{TenantKey}";
|
private string TenantGroupName() => $"messenger:tenant:{TenantKey}";
|
||||||
|
|
|
||||||
|
|
@ -809,7 +809,7 @@ public class ListFormSeeder_Administration : IDataSeedContributor, ITransientDep
|
||||||
EditingOptionJson = DefaultEditingOptionJson(listFormName, 500, 710, true, true, true, true, false),
|
EditingOptionJson = DefaultEditingOptionJson(listFormName, 500, 710, true, true, true, true, false),
|
||||||
EditingFormJson = JsonSerializer.Serialize(new List<EditingFormDto>() {
|
EditingFormJson = JsonSerializer.Serialize(new List<EditingFormDto>() {
|
||||||
new () { Order=1,ColCount=1,ColSpan=1,ItemType="group",Items=[
|
new () { Order=1,ColCount=1,ColSpan=1,ItemType="group",Items=[
|
||||||
new EditingFormItemDto { Order=1, DataField="Avatar", ColSpan=1, EditorType2=EditorTypes.dxImageViewer, EditorOptions=EditorOptionValues.ImageUploadOptions(false) },
|
new EditingFormItemDto { Order=1, DataField="Avatar", ColSpan=1, EditorType2=EditorTypes.dxImageUpload, EditorOptions=EditorOptionValues.ImageUploadOptions(false) },
|
||||||
new EditingFormItemDto { Order=2, DataField="Email", ColSpan=1, EditorType2=EditorTypes.dxTextBox },
|
new EditingFormItemDto { Order=2, DataField="Email", ColSpan=1, EditorType2=EditorTypes.dxTextBox },
|
||||||
new EditingFormItemDto { Order=3, DataField="Name", ColSpan=1, EditorType2=EditorTypes.dxTextBox },
|
new EditingFormItemDto { Order=3, DataField="Name", ColSpan=1, EditorType2=EditorTypes.dxTextBox },
|
||||||
new EditingFormItemDto { Order=4, DataField="Surname", ColSpan=1, EditorType2=EditorTypes.dxTextBox },
|
new EditingFormItemDto { Order=4, DataField="Surname", ColSpan=1, EditorType2=EditorTypes.dxTextBox },
|
||||||
|
|
|
||||||
|
|
@ -85,6 +85,8 @@ public enum TableNameEnum
|
||||||
Event,
|
Event,
|
||||||
EventPhoto,
|
EventPhoto,
|
||||||
EventComment,
|
EventComment,
|
||||||
|
MessengerConversation,
|
||||||
|
MessengerConversationMessage,
|
||||||
Videoroom,
|
Videoroom,
|
||||||
VideoroomParticipant,
|
VideoroomParticipant,
|
||||||
VideoroomAttandance,
|
VideoroomAttandance,
|
||||||
|
|
|
||||||
|
|
@ -104,6 +104,8 @@ public static class TableNameResolver
|
||||||
{ nameof(TableNameEnum.Event), (TablePrefix.TenantByName, MenuPrefix.Administration) },
|
{ nameof(TableNameEnum.Event), (TablePrefix.TenantByName, MenuPrefix.Administration) },
|
||||||
{ nameof(TableNameEnum.EventPhoto), (TablePrefix.TenantByName, MenuPrefix.Administration) },
|
{ nameof(TableNameEnum.EventPhoto), (TablePrefix.TenantByName, MenuPrefix.Administration) },
|
||||||
{ nameof(TableNameEnum.EventComment), (TablePrefix.TenantByName, MenuPrefix.Administration) },
|
{ nameof(TableNameEnum.EventComment), (TablePrefix.TenantByName, MenuPrefix.Administration) },
|
||||||
|
{ nameof(TableNameEnum.MessengerConversation), (TablePrefix.TenantByName, MenuPrefix.Administration) },
|
||||||
|
{ nameof(TableNameEnum.MessengerConversationMessage), (TablePrefix.TenantByName, MenuPrefix.Administration) },
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,29 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using Volo.Abp.Domain.Entities.Auditing;
|
||||||
|
using Volo.Abp.MultiTenancy;
|
||||||
|
|
||||||
|
namespace Sozsoft.Platform.Entities;
|
||||||
|
|
||||||
|
public class MessengerConversation : FullAuditedEntity<Guid>, IMultiTenant
|
||||||
|
{
|
||||||
|
protected MessengerConversation()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public MessengerConversation(Guid id) : base(id)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public Guid? TenantId { get; set; }
|
||||||
|
public string? Title { get; set; }
|
||||||
|
public string ParticipantKey { get; set; } = string.Empty;
|
||||||
|
public string ParticipantIdsJson { get; set; } = "[]";
|
||||||
|
public bool IsGroup { get; set; }
|
||||||
|
public Guid? LastSenderId { get; set; }
|
||||||
|
public string? LastMessagePreview { get; set; }
|
||||||
|
public DateTime? LastMessageTime { get; set; }
|
||||||
|
public int MessageCount { get; set; }
|
||||||
|
|
||||||
|
public virtual ICollection<MessengerConversationMessage> Messages { get; set; } = new List<MessengerConversationMessage>();
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,28 @@
|
||||||
|
using System;
|
||||||
|
using Volo.Abp.Domain.Entities.Auditing;
|
||||||
|
using Volo.Abp.MultiTenancy;
|
||||||
|
|
||||||
|
namespace Sozsoft.Platform.Entities;
|
||||||
|
|
||||||
|
public class MessengerConversationMessage : FullAuditedEntity<Guid>, IMultiTenant
|
||||||
|
{
|
||||||
|
protected MessengerConversationMessage()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public MessengerConversationMessage(Guid id) : base(id)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public Guid? TenantId { get; set; }
|
||||||
|
public Guid ConversationId { get; set; }
|
||||||
|
public Guid SenderId { get; set; }
|
||||||
|
public string SenderUserName { get; set; } = string.Empty;
|
||||||
|
public string SenderName { get; set; } = string.Empty;
|
||||||
|
public string RecipientIdsJson { get; set; } = "[]";
|
||||||
|
public string? Text { get; set; }
|
||||||
|
public string AttachmentsJson { get; set; } = "[]";
|
||||||
|
public DateTime SentAt { get; set; }
|
||||||
|
|
||||||
|
public virtual MessengerConversation Conversation { get; set; } = default!;
|
||||||
|
}
|
||||||
|
|
@ -120,6 +120,9 @@ public class PlatformDbContext :
|
||||||
public DbSet<EventComment> EventComments { get; set; }
|
public DbSet<EventComment> EventComments { get; set; }
|
||||||
public DbSet<EventLike> EventLikes { get; set; }
|
public DbSet<EventLike> EventLikes { get; set; }
|
||||||
|
|
||||||
|
public DbSet<MessengerConversation> MessengerConversations { get; set; }
|
||||||
|
public DbSet<MessengerConversationMessage> MessengerConversationMessages { get; set; }
|
||||||
|
|
||||||
public DbSet<Announcement> Announcements { get; set; }
|
public DbSet<Announcement> Announcements { get; set; }
|
||||||
|
|
||||||
public DbSet<Survey> Surveys { get; set; }
|
public DbSet<Survey> Surveys { get; set; }
|
||||||
|
|
@ -1370,6 +1373,40 @@ public class PlatformDbContext :
|
||||||
.OnDelete(DeleteBehavior.Cascade);
|
.OnDelete(DeleteBehavior.Cascade);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
builder.Entity<MessengerConversation>(b =>
|
||||||
|
{
|
||||||
|
b.ToTable(TableNameResolver.GetFullTableName(nameof(TableNameEnum.MessengerConversation)), Prefix.DbSchema);
|
||||||
|
b.ConfigureByConvention();
|
||||||
|
|
||||||
|
b.Property(x => x.Title).HasMaxLength(256);
|
||||||
|
b.Property(x => x.ParticipantKey).IsRequired().HasMaxLength(512);
|
||||||
|
b.Property(x => x.ParticipantIdsJson).IsRequired().HasColumnType("text");
|
||||||
|
b.Property(x => x.LastMessagePreview).HasMaxLength(512);
|
||||||
|
b.Property(x => x.MessageCount).HasDefaultValue(0);
|
||||||
|
|
||||||
|
b.HasIndex(x => new { x.TenantId, x.ParticipantKey }).IsUnique().HasFilter("[IsDeleted] = 0");
|
||||||
|
|
||||||
|
b.HasMany(x => x.Messages)
|
||||||
|
.WithOne(x => x.Conversation)
|
||||||
|
.HasForeignKey(x => x.ConversationId)
|
||||||
|
.OnDelete(DeleteBehavior.Cascade);
|
||||||
|
});
|
||||||
|
|
||||||
|
builder.Entity<MessengerConversationMessage>(b =>
|
||||||
|
{
|
||||||
|
b.ToTable(TableNameResolver.GetFullTableName(nameof(TableNameEnum.MessengerConversationMessage)), Prefix.DbSchema);
|
||||||
|
b.ConfigureByConvention();
|
||||||
|
|
||||||
|
b.Property(x => x.SenderUserName).IsRequired().HasMaxLength(256);
|
||||||
|
b.Property(x => x.SenderName).IsRequired().HasMaxLength(256);
|
||||||
|
b.Property(x => x.RecipientIdsJson).IsRequired().HasColumnType("text");
|
||||||
|
b.Property(x => x.Text).HasMaxLength(4096);
|
||||||
|
b.Property(x => x.AttachmentsJson).IsRequired().HasColumnType("text");
|
||||||
|
b.Property(x => x.SentAt).IsRequired();
|
||||||
|
|
||||||
|
b.HasIndex(x => new { x.TenantId, x.ConversationId, x.SentAt });
|
||||||
|
});
|
||||||
|
|
||||||
//Videoroom
|
//Videoroom
|
||||||
builder.Entity<Videoroom>(b =>
|
builder.Entity<Videoroom>(b =>
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -13,7 +13,7 @@ using Volo.Abp.EntityFrameworkCore;
|
||||||
namespace Sozsoft.Platform.Migrations
|
namespace Sozsoft.Platform.Migrations
|
||||||
{
|
{
|
||||||
[DbContext(typeof(PlatformDbContext))]
|
[DbContext(typeof(PlatformDbContext))]
|
||||||
[Migration("20260610203148_Initial")]
|
[Migration("20260612183658_Initial")]
|
||||||
partial class Initial
|
partial class Initial
|
||||||
{
|
{
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
|
|
@ -3610,6 +3610,164 @@ namespace Sozsoft.Platform.Migrations
|
||||||
b.ToTable("Sas_H_MenuGroup", (string)null);
|
b.ToTable("Sas_H_MenuGroup", (string)null);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("Sozsoft.Platform.Entities.MessengerConversation", b =>
|
||||||
|
{
|
||||||
|
b.Property<Guid>("Id")
|
||||||
|
.HasColumnType("uniqueidentifier");
|
||||||
|
|
||||||
|
b.Property<DateTime>("CreationTime")
|
||||||
|
.HasColumnType("datetime2")
|
||||||
|
.HasColumnName("CreationTime");
|
||||||
|
|
||||||
|
b.Property<Guid?>("CreatorId")
|
||||||
|
.HasColumnType("uniqueidentifier")
|
||||||
|
.HasColumnName("CreatorId");
|
||||||
|
|
||||||
|
b.Property<Guid?>("DeleterId")
|
||||||
|
.HasColumnType("uniqueidentifier")
|
||||||
|
.HasColumnName("DeleterId");
|
||||||
|
|
||||||
|
b.Property<DateTime?>("DeletionTime")
|
||||||
|
.HasColumnType("datetime2")
|
||||||
|
.HasColumnName("DeletionTime");
|
||||||
|
|
||||||
|
b.Property<bool>("IsDeleted")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("bit")
|
||||||
|
.HasDefaultValue(false)
|
||||||
|
.HasColumnName("IsDeleted");
|
||||||
|
|
||||||
|
b.Property<bool>("IsGroup")
|
||||||
|
.HasColumnType("bit");
|
||||||
|
|
||||||
|
b.Property<string>("LastMessagePreview")
|
||||||
|
.HasMaxLength(512)
|
||||||
|
.HasColumnType("nvarchar(512)");
|
||||||
|
|
||||||
|
b.Property<DateTime?>("LastMessageTime")
|
||||||
|
.HasColumnType("datetime2");
|
||||||
|
|
||||||
|
b.Property<DateTime?>("LastModificationTime")
|
||||||
|
.HasColumnType("datetime2")
|
||||||
|
.HasColumnName("LastModificationTime");
|
||||||
|
|
||||||
|
b.Property<Guid?>("LastModifierId")
|
||||||
|
.HasColumnType("uniqueidentifier")
|
||||||
|
.HasColumnName("LastModifierId");
|
||||||
|
|
||||||
|
b.Property<Guid?>("LastSenderId")
|
||||||
|
.HasColumnType("uniqueidentifier");
|
||||||
|
|
||||||
|
b.Property<int>("MessageCount")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("int")
|
||||||
|
.HasDefaultValue(0);
|
||||||
|
|
||||||
|
b.Property<string>("ParticipantIdsJson")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.Property<string>("ParticipantKey")
|
||||||
|
.IsRequired()
|
||||||
|
.HasMaxLength(512)
|
||||||
|
.HasColumnType("nvarchar(512)");
|
||||||
|
|
||||||
|
b.Property<Guid?>("TenantId")
|
||||||
|
.HasColumnType("uniqueidentifier")
|
||||||
|
.HasColumnName("TenantId");
|
||||||
|
|
||||||
|
b.Property<string>("Title")
|
||||||
|
.HasMaxLength(256)
|
||||||
|
.HasColumnType("nvarchar(256)");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.HasIndex("TenantId", "ParticipantKey")
|
||||||
|
.IsUnique()
|
||||||
|
.HasFilter("[IsDeleted] = 0");
|
||||||
|
|
||||||
|
b.ToTable("Adm_T_MessengerConversation", (string)null);
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("Sozsoft.Platform.Entities.MessengerConversationMessage", b =>
|
||||||
|
{
|
||||||
|
b.Property<Guid>("Id")
|
||||||
|
.HasColumnType("uniqueidentifier");
|
||||||
|
|
||||||
|
b.Property<string>("AttachmentsJson")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.Property<Guid>("ConversationId")
|
||||||
|
.HasColumnType("uniqueidentifier");
|
||||||
|
|
||||||
|
b.Property<DateTime>("CreationTime")
|
||||||
|
.HasColumnType("datetime2")
|
||||||
|
.HasColumnName("CreationTime");
|
||||||
|
|
||||||
|
b.Property<Guid?>("CreatorId")
|
||||||
|
.HasColumnType("uniqueidentifier")
|
||||||
|
.HasColumnName("CreatorId");
|
||||||
|
|
||||||
|
b.Property<Guid?>("DeleterId")
|
||||||
|
.HasColumnType("uniqueidentifier")
|
||||||
|
.HasColumnName("DeleterId");
|
||||||
|
|
||||||
|
b.Property<DateTime?>("DeletionTime")
|
||||||
|
.HasColumnType("datetime2")
|
||||||
|
.HasColumnName("DeletionTime");
|
||||||
|
|
||||||
|
b.Property<bool>("IsDeleted")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("bit")
|
||||||
|
.HasDefaultValue(false)
|
||||||
|
.HasColumnName("IsDeleted");
|
||||||
|
|
||||||
|
b.Property<DateTime?>("LastModificationTime")
|
||||||
|
.HasColumnType("datetime2")
|
||||||
|
.HasColumnName("LastModificationTime");
|
||||||
|
|
||||||
|
b.Property<Guid?>("LastModifierId")
|
||||||
|
.HasColumnType("uniqueidentifier")
|
||||||
|
.HasColumnName("LastModifierId");
|
||||||
|
|
||||||
|
b.Property<string>("RecipientIdsJson")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.Property<Guid>("SenderId")
|
||||||
|
.HasColumnType("uniqueidentifier");
|
||||||
|
|
||||||
|
b.Property<string>("SenderName")
|
||||||
|
.IsRequired()
|
||||||
|
.HasMaxLength(256)
|
||||||
|
.HasColumnType("nvarchar(256)");
|
||||||
|
|
||||||
|
b.Property<string>("SenderUserName")
|
||||||
|
.IsRequired()
|
||||||
|
.HasMaxLength(256)
|
||||||
|
.HasColumnType("nvarchar(256)");
|
||||||
|
|
||||||
|
b.Property<DateTime>("SentAt")
|
||||||
|
.HasColumnType("datetime2");
|
||||||
|
|
||||||
|
b.Property<Guid?>("TenantId")
|
||||||
|
.HasColumnType("uniqueidentifier")
|
||||||
|
.HasColumnName("TenantId");
|
||||||
|
|
||||||
|
b.Property<string>("Text")
|
||||||
|
.HasMaxLength(4096)
|
||||||
|
.HasColumnType("nvarchar(max)");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.HasIndex("ConversationId");
|
||||||
|
|
||||||
|
b.HasIndex("TenantId", "ConversationId", "SentAt");
|
||||||
|
|
||||||
|
b.ToTable("Adm_T_MessengerConversationMessage", (string)null);
|
||||||
|
});
|
||||||
|
|
||||||
modelBuilder.Entity("Sozsoft.Platform.Entities.Note", b =>
|
modelBuilder.Entity("Sozsoft.Platform.Entities.Note", b =>
|
||||||
{
|
{
|
||||||
b.Property<Guid>("Id")
|
b.Property<Guid>("Id")
|
||||||
|
|
@ -8262,6 +8420,17 @@ namespace Sozsoft.Platform.Migrations
|
||||||
.IsRequired();
|
.IsRequired();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("Sozsoft.Platform.Entities.MessengerConversationMessage", b =>
|
||||||
|
{
|
||||||
|
b.HasOne("Sozsoft.Platform.Entities.MessengerConversation", "Conversation")
|
||||||
|
.WithMany("Messages")
|
||||||
|
.HasForeignKey("ConversationId")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
|
b.Navigation("Conversation");
|
||||||
|
});
|
||||||
|
|
||||||
modelBuilder.Entity("Sozsoft.Platform.Entities.OrderItem", b =>
|
modelBuilder.Entity("Sozsoft.Platform.Entities.OrderItem", b =>
|
||||||
{
|
{
|
||||||
b.HasOne("Sozsoft.Platform.Entities.Order", "Order")
|
b.HasOne("Sozsoft.Platform.Entities.Order", "Order")
|
||||||
|
|
@ -8692,6 +8861,11 @@ namespace Sozsoft.Platform.Migrations
|
||||||
b.Navigation("Events");
|
b.Navigation("Events");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("Sozsoft.Platform.Entities.MessengerConversation", b =>
|
||||||
|
{
|
||||||
|
b.Navigation("Messages");
|
||||||
|
});
|
||||||
|
|
||||||
modelBuilder.Entity("Sozsoft.Platform.Entities.Order", b =>
|
modelBuilder.Entity("Sozsoft.Platform.Entities.Order", b =>
|
||||||
{
|
{
|
||||||
b.Navigation("Items");
|
b.Navigation("Items");
|
||||||
|
|
@ -594,6 +594,33 @@ namespace Sozsoft.Platform.Migrations
|
||||||
table.PrimaryKey("PK_Adm_T_IpRestriction", x => x.Id);
|
table.PrimaryKey("PK_Adm_T_IpRestriction", x => x.Id);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
migrationBuilder.CreateTable(
|
||||||
|
name: "Adm_T_MessengerConversation",
|
||||||
|
columns: table => new
|
||||||
|
{
|
||||||
|
Id = table.Column<Guid>(type: "uniqueidentifier", nullable: false),
|
||||||
|
TenantId = table.Column<Guid>(type: "uniqueidentifier", nullable: true),
|
||||||
|
Title = table.Column<string>(type: "nvarchar(256)", maxLength: 256, nullable: true),
|
||||||
|
ParticipantKey = table.Column<string>(type: "nvarchar(512)", maxLength: 512, nullable: false),
|
||||||
|
ParticipantIdsJson = table.Column<string>(type: "text", nullable: false),
|
||||||
|
IsGroup = table.Column<bool>(type: "bit", nullable: false),
|
||||||
|
LastSenderId = table.Column<Guid>(type: "uniqueidentifier", nullable: true),
|
||||||
|
LastMessagePreview = table.Column<string>(type: "nvarchar(512)", maxLength: 512, nullable: true),
|
||||||
|
LastMessageTime = table.Column<DateTime>(type: "datetime2", nullable: true),
|
||||||
|
MessageCount = table.Column<int>(type: "int", nullable: false, defaultValue: 0),
|
||||||
|
CreationTime = table.Column<DateTime>(type: "datetime2", nullable: false),
|
||||||
|
CreatorId = table.Column<Guid>(type: "uniqueidentifier", nullable: true),
|
||||||
|
LastModificationTime = table.Column<DateTime>(type: "datetime2", nullable: true),
|
||||||
|
LastModifierId = table.Column<Guid>(type: "uniqueidentifier", nullable: true),
|
||||||
|
IsDeleted = table.Column<bool>(type: "bit", nullable: false, defaultValue: false),
|
||||||
|
DeleterId = table.Column<Guid>(type: "uniqueidentifier", nullable: true),
|
||||||
|
DeletionTime = table.Column<DateTime>(type: "datetime2", nullable: true)
|
||||||
|
},
|
||||||
|
constraints: table =>
|
||||||
|
{
|
||||||
|
table.PrimaryKey("PK_Adm_T_MessengerConversation", x => x.Id);
|
||||||
|
});
|
||||||
|
|
||||||
migrationBuilder.CreateTable(
|
migrationBuilder.CreateTable(
|
||||||
name: "Adm_T_Note",
|
name: "Adm_T_Note",
|
||||||
columns: table => new
|
columns: table => new
|
||||||
|
|
@ -2118,6 +2145,39 @@ namespace Sozsoft.Platform.Migrations
|
||||||
onDelete: ReferentialAction.Restrict);
|
onDelete: ReferentialAction.Restrict);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
migrationBuilder.CreateTable(
|
||||||
|
name: "Adm_T_MessengerConversationMessage",
|
||||||
|
columns: table => new
|
||||||
|
{
|
||||||
|
Id = table.Column<Guid>(type: "uniqueidentifier", nullable: false),
|
||||||
|
TenantId = table.Column<Guid>(type: "uniqueidentifier", nullable: true),
|
||||||
|
ConversationId = table.Column<Guid>(type: "uniqueidentifier", nullable: false),
|
||||||
|
SenderId = table.Column<Guid>(type: "uniqueidentifier", nullable: false),
|
||||||
|
SenderUserName = table.Column<string>(type: "nvarchar(256)", maxLength: 256, nullable: false),
|
||||||
|
SenderName = table.Column<string>(type: "nvarchar(256)", maxLength: 256, nullable: false),
|
||||||
|
RecipientIdsJson = table.Column<string>(type: "text", nullable: false),
|
||||||
|
Text = table.Column<string>(type: "nvarchar(max)", maxLength: 4096, nullable: true),
|
||||||
|
AttachmentsJson = table.Column<string>(type: "text", nullable: false),
|
||||||
|
SentAt = table.Column<DateTime>(type: "datetime2", nullable: false),
|
||||||
|
CreationTime = table.Column<DateTime>(type: "datetime2", nullable: false),
|
||||||
|
CreatorId = table.Column<Guid>(type: "uniqueidentifier", nullable: true),
|
||||||
|
LastModificationTime = table.Column<DateTime>(type: "datetime2", nullable: true),
|
||||||
|
LastModifierId = table.Column<Guid>(type: "uniqueidentifier", nullable: true),
|
||||||
|
IsDeleted = table.Column<bool>(type: "bit", nullable: false, defaultValue: false),
|
||||||
|
DeleterId = table.Column<Guid>(type: "uniqueidentifier", nullable: true),
|
||||||
|
DeletionTime = table.Column<DateTime>(type: "datetime2", nullable: true)
|
||||||
|
},
|
||||||
|
constraints: table =>
|
||||||
|
{
|
||||||
|
table.PrimaryKey("PK_Adm_T_MessengerConversationMessage", x => x.Id);
|
||||||
|
table.ForeignKey(
|
||||||
|
name: "FK_Adm_T_MessengerConversationMessage_Adm_T_MessengerConversation_ConversationId",
|
||||||
|
column: x => x.ConversationId,
|
||||||
|
principalTable: "Adm_T_MessengerConversation",
|
||||||
|
principalColumn: "Id",
|
||||||
|
onDelete: ReferentialAction.Cascade);
|
||||||
|
});
|
||||||
|
|
||||||
migrationBuilder.CreateTable(
|
migrationBuilder.CreateTable(
|
||||||
name: "Adm_T_ReportTemplate",
|
name: "Adm_T_ReportTemplate",
|
||||||
columns: table => new
|
columns: table => new
|
||||||
|
|
@ -3570,6 +3630,23 @@ namespace Sozsoft.Platform.Migrations
|
||||||
unique: true,
|
unique: true,
|
||||||
filter: "[IsDeleted] = 0");
|
filter: "[IsDeleted] = 0");
|
||||||
|
|
||||||
|
migrationBuilder.CreateIndex(
|
||||||
|
name: "IX_Adm_T_MessengerConversation_TenantId_ParticipantKey",
|
||||||
|
table: "Adm_T_MessengerConversation",
|
||||||
|
columns: new[] { "TenantId", "ParticipantKey" },
|
||||||
|
unique: true,
|
||||||
|
filter: "[IsDeleted] = 0");
|
||||||
|
|
||||||
|
migrationBuilder.CreateIndex(
|
||||||
|
name: "IX_Adm_T_MessengerConversationMessage_ConversationId",
|
||||||
|
table: "Adm_T_MessengerConversationMessage",
|
||||||
|
column: "ConversationId");
|
||||||
|
|
||||||
|
migrationBuilder.CreateIndex(
|
||||||
|
name: "IX_Adm_T_MessengerConversationMessage_TenantId_ConversationId_SentAt",
|
||||||
|
table: "Adm_T_MessengerConversationMessage",
|
||||||
|
columns: new[] { "TenantId", "ConversationId", "SentAt" });
|
||||||
|
|
||||||
migrationBuilder.CreateIndex(
|
migrationBuilder.CreateIndex(
|
||||||
name: "IX_Adm_T_ReportCategory_TenantId_Name",
|
name: "IX_Adm_T_ReportCategory_TenantId_Name",
|
||||||
table: "Adm_T_ReportCategory",
|
table: "Adm_T_ReportCategory",
|
||||||
|
|
@ -4148,6 +4225,9 @@ namespace Sozsoft.Platform.Migrations
|
||||||
migrationBuilder.DropTable(
|
migrationBuilder.DropTable(
|
||||||
name: "Adm_T_JobPosition");
|
name: "Adm_T_JobPosition");
|
||||||
|
|
||||||
|
migrationBuilder.DropTable(
|
||||||
|
name: "Adm_T_MessengerConversationMessage");
|
||||||
|
|
||||||
migrationBuilder.DropTable(
|
migrationBuilder.DropTable(
|
||||||
name: "Adm_T_Note");
|
name: "Adm_T_Note");
|
||||||
|
|
||||||
|
|
@ -4334,6 +4414,9 @@ namespace Sozsoft.Platform.Migrations
|
||||||
migrationBuilder.DropTable(
|
migrationBuilder.DropTable(
|
||||||
name: "Adm_T_Department");
|
name: "Adm_T_Department");
|
||||||
|
|
||||||
|
migrationBuilder.DropTable(
|
||||||
|
name: "Adm_T_MessengerConversation");
|
||||||
|
|
||||||
migrationBuilder.DropTable(
|
migrationBuilder.DropTable(
|
||||||
name: "Adm_T_ReportCategory");
|
name: "Adm_T_ReportCategory");
|
||||||
|
|
||||||
|
|
@ -3607,6 +3607,164 @@ namespace Sozsoft.Platform.Migrations
|
||||||
b.ToTable("Sas_H_MenuGroup", (string)null);
|
b.ToTable("Sas_H_MenuGroup", (string)null);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("Sozsoft.Platform.Entities.MessengerConversation", b =>
|
||||||
|
{
|
||||||
|
b.Property<Guid>("Id")
|
||||||
|
.HasColumnType("uniqueidentifier");
|
||||||
|
|
||||||
|
b.Property<DateTime>("CreationTime")
|
||||||
|
.HasColumnType("datetime2")
|
||||||
|
.HasColumnName("CreationTime");
|
||||||
|
|
||||||
|
b.Property<Guid?>("CreatorId")
|
||||||
|
.HasColumnType("uniqueidentifier")
|
||||||
|
.HasColumnName("CreatorId");
|
||||||
|
|
||||||
|
b.Property<Guid?>("DeleterId")
|
||||||
|
.HasColumnType("uniqueidentifier")
|
||||||
|
.HasColumnName("DeleterId");
|
||||||
|
|
||||||
|
b.Property<DateTime?>("DeletionTime")
|
||||||
|
.HasColumnType("datetime2")
|
||||||
|
.HasColumnName("DeletionTime");
|
||||||
|
|
||||||
|
b.Property<bool>("IsDeleted")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("bit")
|
||||||
|
.HasDefaultValue(false)
|
||||||
|
.HasColumnName("IsDeleted");
|
||||||
|
|
||||||
|
b.Property<bool>("IsGroup")
|
||||||
|
.HasColumnType("bit");
|
||||||
|
|
||||||
|
b.Property<string>("LastMessagePreview")
|
||||||
|
.HasMaxLength(512)
|
||||||
|
.HasColumnType("nvarchar(512)");
|
||||||
|
|
||||||
|
b.Property<DateTime?>("LastMessageTime")
|
||||||
|
.HasColumnType("datetime2");
|
||||||
|
|
||||||
|
b.Property<DateTime?>("LastModificationTime")
|
||||||
|
.HasColumnType("datetime2")
|
||||||
|
.HasColumnName("LastModificationTime");
|
||||||
|
|
||||||
|
b.Property<Guid?>("LastModifierId")
|
||||||
|
.HasColumnType("uniqueidentifier")
|
||||||
|
.HasColumnName("LastModifierId");
|
||||||
|
|
||||||
|
b.Property<Guid?>("LastSenderId")
|
||||||
|
.HasColumnType("uniqueidentifier");
|
||||||
|
|
||||||
|
b.Property<int>("MessageCount")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("int")
|
||||||
|
.HasDefaultValue(0);
|
||||||
|
|
||||||
|
b.Property<string>("ParticipantIdsJson")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.Property<string>("ParticipantKey")
|
||||||
|
.IsRequired()
|
||||||
|
.HasMaxLength(512)
|
||||||
|
.HasColumnType("nvarchar(512)");
|
||||||
|
|
||||||
|
b.Property<Guid?>("TenantId")
|
||||||
|
.HasColumnType("uniqueidentifier")
|
||||||
|
.HasColumnName("TenantId");
|
||||||
|
|
||||||
|
b.Property<string>("Title")
|
||||||
|
.HasMaxLength(256)
|
||||||
|
.HasColumnType("nvarchar(256)");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.HasIndex("TenantId", "ParticipantKey")
|
||||||
|
.IsUnique()
|
||||||
|
.HasFilter("[IsDeleted] = 0");
|
||||||
|
|
||||||
|
b.ToTable("Adm_T_MessengerConversation", (string)null);
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("Sozsoft.Platform.Entities.MessengerConversationMessage", b =>
|
||||||
|
{
|
||||||
|
b.Property<Guid>("Id")
|
||||||
|
.HasColumnType("uniqueidentifier");
|
||||||
|
|
||||||
|
b.Property<string>("AttachmentsJson")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.Property<Guid>("ConversationId")
|
||||||
|
.HasColumnType("uniqueidentifier");
|
||||||
|
|
||||||
|
b.Property<DateTime>("CreationTime")
|
||||||
|
.HasColumnType("datetime2")
|
||||||
|
.HasColumnName("CreationTime");
|
||||||
|
|
||||||
|
b.Property<Guid?>("CreatorId")
|
||||||
|
.HasColumnType("uniqueidentifier")
|
||||||
|
.HasColumnName("CreatorId");
|
||||||
|
|
||||||
|
b.Property<Guid?>("DeleterId")
|
||||||
|
.HasColumnType("uniqueidentifier")
|
||||||
|
.HasColumnName("DeleterId");
|
||||||
|
|
||||||
|
b.Property<DateTime?>("DeletionTime")
|
||||||
|
.HasColumnType("datetime2")
|
||||||
|
.HasColumnName("DeletionTime");
|
||||||
|
|
||||||
|
b.Property<bool>("IsDeleted")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("bit")
|
||||||
|
.HasDefaultValue(false)
|
||||||
|
.HasColumnName("IsDeleted");
|
||||||
|
|
||||||
|
b.Property<DateTime?>("LastModificationTime")
|
||||||
|
.HasColumnType("datetime2")
|
||||||
|
.HasColumnName("LastModificationTime");
|
||||||
|
|
||||||
|
b.Property<Guid?>("LastModifierId")
|
||||||
|
.HasColumnType("uniqueidentifier")
|
||||||
|
.HasColumnName("LastModifierId");
|
||||||
|
|
||||||
|
b.Property<string>("RecipientIdsJson")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.Property<Guid>("SenderId")
|
||||||
|
.HasColumnType("uniqueidentifier");
|
||||||
|
|
||||||
|
b.Property<string>("SenderName")
|
||||||
|
.IsRequired()
|
||||||
|
.HasMaxLength(256)
|
||||||
|
.HasColumnType("nvarchar(256)");
|
||||||
|
|
||||||
|
b.Property<string>("SenderUserName")
|
||||||
|
.IsRequired()
|
||||||
|
.HasMaxLength(256)
|
||||||
|
.HasColumnType("nvarchar(256)");
|
||||||
|
|
||||||
|
b.Property<DateTime>("SentAt")
|
||||||
|
.HasColumnType("datetime2");
|
||||||
|
|
||||||
|
b.Property<Guid?>("TenantId")
|
||||||
|
.HasColumnType("uniqueidentifier")
|
||||||
|
.HasColumnName("TenantId");
|
||||||
|
|
||||||
|
b.Property<string>("Text")
|
||||||
|
.HasMaxLength(4096)
|
||||||
|
.HasColumnType("nvarchar(max)");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.HasIndex("ConversationId");
|
||||||
|
|
||||||
|
b.HasIndex("TenantId", "ConversationId", "SentAt");
|
||||||
|
|
||||||
|
b.ToTable("Adm_T_MessengerConversationMessage", (string)null);
|
||||||
|
});
|
||||||
|
|
||||||
modelBuilder.Entity("Sozsoft.Platform.Entities.Note", b =>
|
modelBuilder.Entity("Sozsoft.Platform.Entities.Note", b =>
|
||||||
{
|
{
|
||||||
b.Property<Guid>("Id")
|
b.Property<Guid>("Id")
|
||||||
|
|
@ -8259,6 +8417,17 @@ namespace Sozsoft.Platform.Migrations
|
||||||
.IsRequired();
|
.IsRequired();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("Sozsoft.Platform.Entities.MessengerConversationMessage", b =>
|
||||||
|
{
|
||||||
|
b.HasOne("Sozsoft.Platform.Entities.MessengerConversation", "Conversation")
|
||||||
|
.WithMany("Messages")
|
||||||
|
.HasForeignKey("ConversationId")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
|
b.Navigation("Conversation");
|
||||||
|
});
|
||||||
|
|
||||||
modelBuilder.Entity("Sozsoft.Platform.Entities.OrderItem", b =>
|
modelBuilder.Entity("Sozsoft.Platform.Entities.OrderItem", b =>
|
||||||
{
|
{
|
||||||
b.HasOne("Sozsoft.Platform.Entities.Order", "Order")
|
b.HasOne("Sozsoft.Platform.Entities.Order", "Order")
|
||||||
|
|
@ -8689,6 +8858,11 @@ namespace Sozsoft.Platform.Migrations
|
||||||
b.Navigation("Events");
|
b.Navigation("Events");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("Sozsoft.Platform.Entities.MessengerConversation", b =>
|
||||||
|
{
|
||||||
|
b.Navigation("Messages");
|
||||||
|
});
|
||||||
|
|
||||||
modelBuilder.Entity("Sozsoft.Platform.Entities.Order", b =>
|
modelBuilder.Entity("Sozsoft.Platform.Entities.Order", b =>
|
||||||
{
|
{
|
||||||
b.Navigation("Items");
|
b.Navigation("Items");
|
||||||
|
|
|
||||||
Binary file not shown.
|
Before Width: | Height: | Size: 3.8 KiB After Width: | Height: | Size: 12 KiB |
|
|
@ -1,5 +1,5 @@
|
||||||
{
|
{
|
||||||
"commit": "6098758",
|
"commit": "7632c9e",
|
||||||
"releases": [
|
"releases": [
|
||||||
{
|
{
|
||||||
"version": "1.1.04",
|
"version": "1.1.04",
|
||||||
|
|
|
||||||
|
|
@ -7,16 +7,27 @@ import { toast } from '@/components/ui'
|
||||||
import { AVATAR_URL } from '@/constants/app.constant'
|
import { AVATAR_URL } from '@/constants/app.constant'
|
||||||
import {
|
import {
|
||||||
getMessengerContacts,
|
getMessengerContacts,
|
||||||
|
getMessengerMessages,
|
||||||
MessengerAttachmentDto,
|
MessengerAttachmentDto,
|
||||||
MessengerContactDto,
|
MessengerContactDto,
|
||||||
|
MessengerMessageDto,
|
||||||
uploadMessengerAttachment,
|
uploadMessengerAttachment,
|
||||||
} from '@/services/messenger.service'
|
} from '@/services/messenger.service'
|
||||||
import { messengerSignalR, MessengerMessageDto } from '@/services/messenger.signalr'
|
import { messengerSignalR } from '@/services/messenger.signalr'
|
||||||
import { useStoreState } from '@/store'
|
import { useStoreState } from '@/store'
|
||||||
import dayjs from 'dayjs'
|
import dayjs from 'dayjs'
|
||||||
import EmojiPicker, { EmojiClickData } from 'emoji-picker-react'
|
import EmojiPicker, { EmojiClickData } from 'emoji-picker-react'
|
||||||
import { useEffect, useMemo, useRef, useState } from 'react'
|
import { useEffect, useMemo, useRef, useState } from 'react'
|
||||||
import { FaArrowLeft, FaPaperclip, FaRegSmile, FaSearch, FaTimes, FaTrash } from 'react-icons/fa'
|
import {
|
||||||
|
FaArrowLeft,
|
||||||
|
FaCompress,
|
||||||
|
FaExpand,
|
||||||
|
FaPaperclip,
|
||||||
|
FaRegSmile,
|
||||||
|
FaSearch,
|
||||||
|
FaTimes,
|
||||||
|
FaTrash,
|
||||||
|
} from 'react-icons/fa'
|
||||||
import { IoCheckmarkDone, IoChatbubbleEllipsesOutline, IoSend } from 'react-icons/io5'
|
import { IoCheckmarkDone, IoChatbubbleEllipsesOutline, IoSend } from 'react-icons/io5'
|
||||||
|
|
||||||
const MAX_UPLOAD_SIZE = 10 * 1024 * 1024
|
const MAX_UPLOAD_SIZE = 10 * 1024 * 1024
|
||||||
|
|
@ -26,10 +37,14 @@ const formatFileSize = (size: number) => {
|
||||||
return `${(size / (1024 * 1024)).toFixed(1)} MB`
|
return `${(size / (1024 * 1024)).toFixed(1)} MB`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const canDeleteMessage = (message: MessengerMessageDto, userId: string) =>
|
||||||
|
message.senderId === userId && dayjs().diff(dayjs(message.sentAt), 'minute', true) < 10
|
||||||
|
|
||||||
const MessengerWidget = () => {
|
const MessengerWidget = () => {
|
||||||
const auth = useStoreState((state) => state.auth)
|
const auth = useStoreState((state) => state.auth)
|
||||||
const tenantId = auth.user.tenantId || auth.tenant?.tenantId
|
const tenantId = auth.user.tenantId || auth.tenant?.tenantId
|
||||||
const fileInputRef = useRef<HTMLInputElement>(null)
|
const fileInputRef = useRef<HTMLInputElement>(null)
|
||||||
|
const messageInputRef = useRef<HTMLTextAreaElement>(null)
|
||||||
const messagesEndRef = useRef<HTMLDivElement>(null)
|
const messagesEndRef = useRef<HTMLDivElement>(null)
|
||||||
|
|
||||||
const [open, setOpen] = useState(false)
|
const [open, setOpen] = useState(false)
|
||||||
|
|
@ -43,25 +58,38 @@ const MessengerWidget = () => {
|
||||||
const [uploading, setUploading] = useState(false)
|
const [uploading, setUploading] = useState(false)
|
||||||
const [showEmoji, setShowEmoji] = useState(false)
|
const [showEmoji, setShowEmoji] = useState(false)
|
||||||
const [multiSelect, setMultiSelect] = useState(false)
|
const [multiSelect, setMultiSelect] = useState(false)
|
||||||
const [unread, setUnread] = useState(0)
|
const [maximized, setMaximized] = useState(false)
|
||||||
|
const [unreadByContact, setUnreadByContact] = useState<Record<string, number>>({})
|
||||||
|
|
||||||
|
const unread = useMemo(
|
||||||
|
() => Object.values(unreadByContact).reduce((total, count) => total + count, 0),
|
||||||
|
[unreadByContact],
|
||||||
|
)
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!auth.session.signedIn) return
|
if (!auth.session.signedIn) return
|
||||||
|
|
||||||
const unsubscribeMessage = messengerSignalR.onMessage((message) => {
|
const unsubscribeMessage = messengerSignalR.onMessage((message) => {
|
||||||
setMessages((prev) => (prev.some((item) => item.id === message.id) ? prev : [...prev, message]))
|
setMessages((prev) => (prev.some((item) => item.id === message.id) ? prev : [...prev, message]))
|
||||||
if (!open && message.senderId !== auth.user.id) {
|
if (message.senderId !== auth.user.id && (!open || !selectedIds.includes(message.senderId))) {
|
||||||
setUnread((count) => count + 1)
|
setUnreadByContact((current) => ({
|
||||||
|
...current,
|
||||||
|
[message.senderId]: (current[message.senderId] || 0) + 1,
|
||||||
|
}))
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
const unsubscribeMessageDeleted = messengerSignalR.onMessageDeleted((message) => {
|
||||||
|
setMessages((prev) => prev.filter((item) => item.id !== message.messageId))
|
||||||
|
})
|
||||||
const unsubscribeState = messengerSignalR.onStateChange(setConnected)
|
const unsubscribeState = messengerSignalR.onStateChange(setConnected)
|
||||||
messengerSignalR.start()
|
messengerSignalR.start()
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
unsubscribeMessage()
|
unsubscribeMessage()
|
||||||
|
unsubscribeMessageDeleted()
|
||||||
unsubscribeState()
|
unsubscribeState()
|
||||||
}
|
}
|
||||||
}, [auth.session.signedIn, auth.user.id, open])
|
}, [auth.session.signedIn, auth.user.id, open, selectedIds])
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!auth.session.signedIn) {
|
if (!auth.session.signedIn) {
|
||||||
|
|
@ -71,7 +99,6 @@ const MessengerWidget = () => {
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!open) return
|
if (!open) return
|
||||||
setUnread(0)
|
|
||||||
getMessengerContacts(filter)
|
getMessengerContacts(filter)
|
||||||
.then((response) => setContacts(response.data))
|
.then((response) => setContacts(response.data))
|
||||||
.catch(() => setContacts([]))
|
.catch(() => setContacts([]))
|
||||||
|
|
@ -81,13 +108,35 @@ const MessengerWidget = () => {
|
||||||
messagesEndRef.current?.scrollIntoView({ behavior: 'smooth' })
|
messagesEndRef.current?.scrollIntoView({ behavior: 'smooth' })
|
||||||
}, [messages, open])
|
}, [messages, open])
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!open || selectedIds.length === 0) return
|
||||||
|
|
||||||
|
const timer = window.setTimeout(() => {
|
||||||
|
messageInputRef.current?.focus()
|
||||||
|
}, 0)
|
||||||
|
|
||||||
|
return () => window.clearTimeout(timer)
|
||||||
|
}, [open, selectedIds])
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!open || selectedIds.length === 0) return
|
||||||
|
|
||||||
|
getMessengerMessages({
|
||||||
|
participantIds: selectedIds,
|
||||||
|
maxResultCount: 50,
|
||||||
|
sorting: 'sentAt desc',
|
||||||
|
})
|
||||||
|
.then((response) => setMessages(response.data))
|
||||||
|
.catch(() => setMessages([]))
|
||||||
|
}, [open, selectedIds])
|
||||||
|
|
||||||
const selectedContacts = useMemo(
|
const selectedContacts = useMemo(
|
||||||
() => contacts.filter((contact) => selectedIds.includes(contact.id)),
|
() => contacts.filter((contact) => selectedIds.includes(contact.id)),
|
||||||
[contacts, selectedIds],
|
[contacts, selectedIds],
|
||||||
)
|
)
|
||||||
|
|
||||||
const visibleMessages = useMemo(() => {
|
const visibleMessages = useMemo(() => {
|
||||||
if (selectedIds.length === 0) return messages.slice(-30)
|
if (selectedIds.length === 0) return []
|
||||||
|
|
||||||
return messages.filter((message) => {
|
return messages.filter((message) => {
|
||||||
const isOwn = message.senderId === auth.user.id
|
const isOwn = message.senderId === auth.user.id
|
||||||
|
|
@ -97,6 +146,14 @@ const MessengerWidget = () => {
|
||||||
}, [auth.user.id, messages, selectedIds])
|
}, [auth.user.id, messages, selectedIds])
|
||||||
|
|
||||||
const toggleContact = (id: string) => {
|
const toggleContact = (id: string) => {
|
||||||
|
setUnreadByContact((current) => {
|
||||||
|
if (!current[id]) return current
|
||||||
|
|
||||||
|
const next = { ...current }
|
||||||
|
delete next[id]
|
||||||
|
return next
|
||||||
|
})
|
||||||
|
|
||||||
if (!multiSelect) {
|
if (!multiSelect) {
|
||||||
setSelectedIds([id])
|
setSelectedIds([id])
|
||||||
return
|
return
|
||||||
|
|
@ -114,6 +171,16 @@ const MessengerWidget = () => {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const closeWidget = () => {
|
||||||
|
setOpen(false)
|
||||||
|
setMaximized(false)
|
||||||
|
setSelectedIds([])
|
||||||
|
setMessages([])
|
||||||
|
setText('')
|
||||||
|
setAttachments([])
|
||||||
|
setShowEmoji(false)
|
||||||
|
}
|
||||||
|
|
||||||
const handleFiles = async (files: FileList | null) => {
|
const handleFiles = async (files: FileList | null) => {
|
||||||
if (!files?.length) return
|
if (!files?.length) return
|
||||||
|
|
||||||
|
|
@ -153,21 +220,38 @@ const MessengerWidget = () => {
|
||||||
setText('')
|
setText('')
|
||||||
setAttachments([])
|
setAttachments([])
|
||||||
setShowEmoji(false)
|
setShowEmoji(false)
|
||||||
|
window.setTimeout(() => {
|
||||||
|
messageInputRef.current?.focus()
|
||||||
|
}, 0)
|
||||||
} catch {
|
} catch {
|
||||||
toast.push(<Notification title="Mesaj gönderilemedi" type="danger" />, { placement: 'top-end' })
|
toast.push(<Notification title="Mesaj gönderilemedi" type="danger" />, { placement: 'top-end' })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const deleteMessage = async (messageId: string) => {
|
||||||
|
if (!window.confirm('Mesaj silinsin mi?')) return
|
||||||
|
|
||||||
|
try {
|
||||||
|
await messengerSignalR.deleteMessage(messageId)
|
||||||
|
} catch {
|
||||||
|
toast.push(<Notification title="Mesaj silinemedi" type="danger" />, { placement: 'top-end' })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const onEmojiClick = (emoji: EmojiClickData) => {
|
const onEmojiClick = (emoji: EmojiClickData) => {
|
||||||
setText((current) => `${current}${emoji.emoji}`)
|
setText((current) => `${current}${emoji.emoji}`)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!auth.session.signedIn) return null
|
if (!auth.session.signedIn) return null
|
||||||
|
|
||||||
|
const panelClassName = maximized
|
||||||
|
? 'pointer-events-auto fixed inset-2 overflow-hidden rounded-lg border border-gray-200 bg-white shadow-2xl dark:border-gray-700 dark:bg-gray-800 sm:inset-4'
|
||||||
|
: 'pointer-events-auto fixed inset-x-2 bottom-20 top-3 overflow-hidden rounded-lg border border-gray-200 bg-white shadow-2xl dark:border-gray-700 dark:bg-gray-800 sm:absolute sm:inset-auto sm:bottom-16 sm:right-0 sm:h-[min(70vh,640px)] sm:w-[min(78vw,800px)]'
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="pointer-events-none fixed bottom-5 right-5 z-50 h-14 w-14">
|
<div className="pointer-events-none fixed bottom-5 right-5 z-50 h-14 w-14">
|
||||||
{open && (
|
{open && (
|
||||||
<div className="pointer-events-auto fixed inset-x-2 bottom-20 top-3 overflow-hidden rounded-lg border border-gray-200 bg-white shadow-2xl dark:border-gray-700 dark:bg-gray-800 sm:absolute sm:inset-auto sm:bottom-16 sm:right-0 sm:h-[min(70vh,640px)] sm:w-[min(78vw,800px)]">
|
<div className={panelClassName}>
|
||||||
<div className="flex h-full min-h-0">
|
<div className="flex h-full min-h-0">
|
||||||
<aside
|
<aside
|
||||||
className={`min-h-0 w-full shrink-0 flex-col border-gray-200 bg-gray-50 dark:border-gray-700 dark:bg-gray-900 sm:flex sm:w-72 sm:border-r ${
|
className={`min-h-0 w-full shrink-0 flex-col border-gray-200 bg-gray-50 dark:border-gray-700 dark:bg-gray-900 sm:flex sm:w-72 sm:border-r ${
|
||||||
|
|
@ -187,7 +271,17 @@ const MessengerWidget = () => {
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<Button size="xs" variant="plain" icon={<FaTimes />} onClick={() => setOpen(false)} />
|
<div className="flex shrink-0 items-center gap-1 sm:hidden">
|
||||||
|
<Tooltip title={maximized ? 'Küçült' : 'Büyüt'}>
|
||||||
|
<Button
|
||||||
|
size="xs"
|
||||||
|
variant="plain"
|
||||||
|
icon={maximized ? <FaCompress /> : <FaExpand />}
|
||||||
|
onClick={() => setMaximized((value) => !value)}
|
||||||
|
/>
|
||||||
|
</Tooltip>
|
||||||
|
<Button size="xs" variant="plain" icon={<FaTimes />} onClick={closeWidget} />
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="space-y-2 p-3">
|
<div className="space-y-2 p-3">
|
||||||
|
|
@ -211,6 +305,7 @@ const MessengerWidget = () => {
|
||||||
<div className="min-h-0 flex-1 overflow-y-auto px-2 pb-2">
|
<div className="min-h-0 flex-1 overflow-y-auto px-2 pb-2">
|
||||||
{contacts.map((contact) => {
|
{contacts.map((contact) => {
|
||||||
const active = selectedIds.includes(contact.id)
|
const active = selectedIds.includes(contact.id)
|
||||||
|
const contactUnread = unreadByContact[contact.id] || 0
|
||||||
return (
|
return (
|
||||||
<button
|
<button
|
||||||
key={contact.id}
|
key={contact.id}
|
||||||
|
|
@ -227,7 +322,12 @@ const MessengerWidget = () => {
|
||||||
<span className="block truncate text-sm font-medium">{contact.fullName}</span>
|
<span className="block truncate text-sm font-medium">{contact.fullName}</span>
|
||||||
<span className="block truncate text-xs text-gray-500">{contact.email || contact.userName}</span>
|
<span className="block truncate text-xs text-gray-500">{contact.email || contact.userName}</span>
|
||||||
</span>
|
</span>
|
||||||
{active && <IoCheckmarkDone className="shrink-0 text-lg" />}
|
{contactUnread > 0 && (
|
||||||
|
<span className="shrink-0 rounded-full bg-red-500 px-2 py-0.5 text-xs font-semibold text-white">
|
||||||
|
{contactUnread > 99 ? '99+' : contactUnread}
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
{active && contactUnread === 0 && <IoCheckmarkDone className="shrink-0 text-lg" />}
|
||||||
</button>
|
</button>
|
||||||
)
|
)
|
||||||
})}
|
})}
|
||||||
|
|
@ -277,6 +377,17 @@ const MessengerWidget = () => {
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div className="flex shrink-0 items-center gap-1">
|
||||||
|
<Tooltip title={maximized ? 'Küçült' : 'Büyüt'}>
|
||||||
|
<Button
|
||||||
|
size="xs"
|
||||||
|
variant="plain"
|
||||||
|
icon={maximized ? <FaCompress /> : <FaExpand />}
|
||||||
|
onClick={() => setMaximized((value) => !value)}
|
||||||
|
/>
|
||||||
|
</Tooltip>
|
||||||
|
<Button size="xs" variant="plain" icon={<FaTimes />} onClick={closeWidget} />
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="flex-1 overflow-y-auto bg-white px-4 py-3 dark:bg-gray-800">
|
<div className="flex-1 overflow-y-auto bg-white px-4 py-3 dark:bg-gray-800">
|
||||||
|
|
@ -288,16 +399,31 @@ const MessengerWidget = () => {
|
||||||
|
|
||||||
{visibleMessages.map((message) => {
|
{visibleMessages.map((message) => {
|
||||||
const own = message.senderId === auth.user.id
|
const own = message.senderId === auth.user.id
|
||||||
|
const deletable = canDeleteMessage(message, auth.user.id)
|
||||||
return (
|
return (
|
||||||
<div key={message.id} className={`mb-3 flex ${own ? 'justify-end' : 'justify-start'}`}>
|
<div key={message.id} className={`mb-3 flex ${own ? 'justify-end' : 'justify-start'}`}>
|
||||||
<div
|
<div className={`group flex max-w-[76%] items-start gap-1.5 ${own ? 'flex-row-reverse' : ''}`}>
|
||||||
className={`max-w-[76%] rounded-lg px-3 py-2 shadow-sm ${
|
<div
|
||||||
own
|
className={`rounded-lg px-3 py-2 shadow-sm ${
|
||||||
? 'bg-emerald-600 text-white'
|
own
|
||||||
: 'bg-gray-100 text-gray-900 dark:bg-gray-700 dark:text-gray-100'
|
? 'bg-emerald-600 text-white'
|
||||||
}`}
|
: 'bg-gray-100 text-gray-900 dark:bg-gray-700 dark:text-gray-100'
|
||||||
>
|
}`}
|
||||||
{!own && <div className="mb-1 text-xs font-semibold">{message.senderName}</div>}
|
>
|
||||||
|
<div className="mb-1 flex items-center gap-1.5 text-xs font-semibold">
|
||||||
|
<Avatar
|
||||||
|
size={20}
|
||||||
|
shape="circle"
|
||||||
|
src={
|
||||||
|
own
|
||||||
|
? auth.user.avatar || AVATAR_URL(auth.user.id, tenantId)
|
||||||
|
: AVATAR_URL(message.senderId, message.tenantId || tenantId)
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
<span className="max-w-[160px] truncate">
|
||||||
|
{own ? auth.user.name || auth.user.userName || 'Ben' : message.senderName}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
{message.text && <div className="whitespace-pre-wrap text-sm leading-5">{message.text}</div>}
|
{message.text && <div className="whitespace-pre-wrap text-sm leading-5">{message.text}</div>}
|
||||||
{message.attachments.length > 0 && (
|
{message.attachments.length > 0 && (
|
||||||
<div className="mt-2 space-y-1">
|
<div className="mt-2 space-y-1">
|
||||||
|
|
@ -317,9 +443,24 @@ const MessengerWidget = () => {
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
<div className={`mt-1 text-[11px] ${own ? 'text-emerald-100' : 'text-gray-500'}`}>
|
<div
|
||||||
{dayjs(message.sentAt).format('HH:mm')}
|
className={`mt-1 flex items-center gap-2 text-[11px] ${
|
||||||
|
own ? 'justify-end text-emerald-100' : 'justify-start text-gray-500'
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
<span>{dayjs(message.sentAt).format('HH:mm')}</span>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
{own && deletable && (
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
title="Mesaji sil"
|
||||||
|
className="mt-1 flex h-5 w-5 shrink-0 items-center justify-center rounded-full text-[10px] text-gray-400 opacity-0 transition hover:bg-red-500 hover:text-white group-hover:opacity-100 focus:opacity-100"
|
||||||
|
onClick={() => deleteMessage(message.id)}
|
||||||
|
>
|
||||||
|
<FaTrash />
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
|
|
@ -391,6 +532,7 @@ const MessengerWidget = () => {
|
||||||
/>
|
/>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
<textarea
|
<textarea
|
||||||
|
ref={messageInputRef}
|
||||||
value={text}
|
value={text}
|
||||||
placeholder="Mesaj yazın"
|
placeholder="Mesaj yazın"
|
||||||
rows={1}
|
rows={1}
|
||||||
|
|
@ -424,12 +566,12 @@ const MessengerWidget = () => {
|
||||||
<Tooltip title="Messenger">
|
<Tooltip title="Messenger">
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
onClick={() => setOpen((value) => !value)}
|
onClick={() => (open ? closeWidget() : setOpen(true))}
|
||||||
className="pointer-events-auto relative flex h-12 w-12 items-center justify-center rounded-full bg-emerald-600 text-2xl text-white shadow-xl transition hover:bg-emerald-700"
|
className="pointer-events-auto relative flex h-12 w-12 items-center justify-center rounded-full bg-emerald-600 text-2xl text-white shadow-xl transition hover:bg-emerald-700"
|
||||||
>
|
>
|
||||||
<IoChatbubbleEllipsesOutline />
|
<IoChatbubbleEllipsesOutline />
|
||||||
{unread > 0 && (
|
{unread > 0 && (
|
||||||
<span className="absolute min-w-[20px] rounded-full bg-red-500 px-1.5 py-0.5 text-xs font-semibold text-white">
|
<span className="absolute -right-1 -top-1 flex min-h-[20px] min-w-[20px] items-center justify-center rounded-full bg-red-500 px-1.5 text-xs font-semibold leading-none text-white ring-2 ring-white dark:ring-gray-800">
|
||||||
{unread > 99 ? '99+' : unread}
|
{unread > 99 ? '99+' : unread}
|
||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
|
|
|
||||||
|
|
@ -19,6 +19,34 @@ export interface MessengerAttachmentDto {
|
||||||
url: string
|
url: string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface MessengerGetMessagesInput {
|
||||||
|
conversationId?: string
|
||||||
|
participantIds: string[]
|
||||||
|
skipCount?: number
|
||||||
|
maxResultCount?: number
|
||||||
|
sorting?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface MessengerMessageDto {
|
||||||
|
id: string
|
||||||
|
tenantId?: string
|
||||||
|
conversationId: string
|
||||||
|
senderId: string
|
||||||
|
senderUserName: string
|
||||||
|
senderName: string
|
||||||
|
recipientIds: string[]
|
||||||
|
text?: string
|
||||||
|
attachments: MessengerAttachmentDto[]
|
||||||
|
sentAt: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface MessengerMessageDeletedDto {
|
||||||
|
messageId: string
|
||||||
|
conversationId: string
|
||||||
|
senderId: string
|
||||||
|
recipientIds: string[]
|
||||||
|
}
|
||||||
|
|
||||||
export const getMessengerContacts = (filter?: string) =>
|
export const getMessengerContacts = (filter?: string) =>
|
||||||
apiService.fetchData<MessengerContactDto[]>({
|
apiService.fetchData<MessengerContactDto[]>({
|
||||||
method: 'GET',
|
method: 'GET',
|
||||||
|
|
@ -26,6 +54,13 @@ export const getMessengerContacts = (filter?: string) =>
|
||||||
params: filter ? { filter } : undefined,
|
params: filter ? { filter } : undefined,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
export const getMessengerMessages = (data: MessengerGetMessagesInput) =>
|
||||||
|
apiService.fetchData<MessengerMessageDto[]>({
|
||||||
|
method: 'POST',
|
||||||
|
url: '/api/app/messenger/messages',
|
||||||
|
data,
|
||||||
|
})
|
||||||
|
|
||||||
export const uploadMessengerAttachment = (file: File) => {
|
export const uploadMessengerAttachment = (file: File) => {
|
||||||
const formData = new FormData()
|
const formData = new FormData()
|
||||||
formData.append('file', file)
|
formData.append('file', file)
|
||||||
|
|
|
||||||
|
|
@ -1,31 +1,22 @@
|
||||||
import { store } from '@/store/store'
|
import { store } from '@/store/store'
|
||||||
import * as signalR from '@microsoft/signalr'
|
import * as signalR from '@microsoft/signalr'
|
||||||
import { MessengerAttachmentDto } from './messenger.service'
|
import { MessengerAttachmentDto, MessengerMessageDeletedDto, MessengerMessageDto } from './messenger.service'
|
||||||
|
|
||||||
export interface MessengerMessageDto {
|
|
||||||
id: string
|
|
||||||
tenantId?: string
|
|
||||||
senderId: string
|
|
||||||
senderUserName: string
|
|
||||||
senderName: string
|
|
||||||
recipientIds: string[]
|
|
||||||
text?: string
|
|
||||||
attachments: MessengerAttachmentDto[]
|
|
||||||
sentAt: string
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface MessengerSendMessageDto {
|
export interface MessengerSendMessageDto {
|
||||||
|
conversationId?: string
|
||||||
recipientIds: string[]
|
recipientIds: string[]
|
||||||
text?: string
|
text?: string
|
||||||
attachments: MessengerAttachmentDto[]
|
attachments: MessengerAttachmentDto[]
|
||||||
}
|
}
|
||||||
|
|
||||||
type MessageHandler = (message: MessengerMessageDto) => void
|
type MessageHandler = (message: MessengerMessageDto) => void
|
||||||
|
type MessageDeletedHandler = (message: MessengerMessageDeletedDto) => void
|
||||||
type StateHandler = (connected: boolean) => void
|
type StateHandler = (connected: boolean) => void
|
||||||
|
|
||||||
class MessengerSignalRService {
|
class MessengerSignalRService {
|
||||||
private connection?: signalR.HubConnection
|
private connection?: signalR.HubConnection
|
||||||
private messageHandlers = new Set<MessageHandler>()
|
private messageHandlers = new Set<MessageHandler>()
|
||||||
|
private messageDeletedHandlers = new Set<MessageDeletedHandler>()
|
||||||
private stateHandlers = new Set<StateHandler>()
|
private stateHandlers = new Set<StateHandler>()
|
||||||
|
|
||||||
private createConnection() {
|
private createConnection() {
|
||||||
|
|
@ -43,6 +34,10 @@ class MessengerSignalRService {
|
||||||
this.messageHandlers.forEach((handler) => handler(message))
|
this.messageHandlers.forEach((handler) => handler(message))
|
||||||
})
|
})
|
||||||
|
|
||||||
|
this.connection.on('MessengerMessageDeleted', (message: MessengerMessageDeletedDto) => {
|
||||||
|
this.messageDeletedHandlers.forEach((handler) => handler(message))
|
||||||
|
})
|
||||||
|
|
||||||
this.connection.onreconnected(() => this.emitState(true))
|
this.connection.onreconnected(() => this.emitState(true))
|
||||||
this.connection.onreconnecting(() => this.emitState(false))
|
this.connection.onreconnecting(() => this.emitState(false))
|
||||||
this.connection.onclose(() => this.emitState(false))
|
this.connection.onclose(() => this.emitState(false))
|
||||||
|
|
@ -87,11 +82,25 @@ class MessengerSignalRService {
|
||||||
await this.connection.invoke('SendMessage', input)
|
await this.connection.invoke('SendMessage', input)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async deleteMessage(messageId: string) {
|
||||||
|
await this.start()
|
||||||
|
if (this.connection?.state !== signalR.HubConnectionState.Connected) {
|
||||||
|
throw new Error('Messenger bağlantısı yok')
|
||||||
|
}
|
||||||
|
|
||||||
|
await this.connection.invoke('DeleteMessage', messageId)
|
||||||
|
}
|
||||||
|
|
||||||
onMessage(handler: MessageHandler) {
|
onMessage(handler: MessageHandler) {
|
||||||
this.messageHandlers.add(handler)
|
this.messageHandlers.add(handler)
|
||||||
return () => this.messageHandlers.delete(handler)
|
return () => this.messageHandlers.delete(handler)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
onMessageDeleted(handler: MessageDeletedHandler) {
|
||||||
|
this.messageDeletedHandlers.add(handler)
|
||||||
|
return () => this.messageDeletedHandlers.delete(handler)
|
||||||
|
}
|
||||||
|
|
||||||
onStateChange(handler: StateHandler) {
|
onStateChange(handler: StateHandler) {
|
||||||
this.stateHandlers.add(handler)
|
this.stateHandlers.add(handler)
|
||||||
handler(this.getConnectionState())
|
handler(this.getConnectionState())
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue