diff --git a/.idea/.idea.Eclipse/.idea/dataSources/30725304-56d3-46dd-a0f3-bd883f7c02e7.xml b/.idea/.idea.Eclipse/.idea/dataSources/30725304-56d3-46dd-a0f3-bd883f7c02e7.xml
index ad21724..16c6ac5 100644
--- a/.idea/.idea.Eclipse/.idea/dataSources/30725304-56d3-46dd-a0f3-bd883f7c02e7.xml
+++ b/.idea/.idea.Eclipse/.idea/dataSources/30725304-56d3-46dd-a0f3-bd883f7c02e7.xml
@@ -3,7 +3,7 @@
mdy
- 813
+ 850
16.4
1728973241
true ACDT
@@ -1404,7 +1404,7 @@ false posixrules
13212||10|C|G
13212||-9223372036854775808|U|G
13212||10|U|G
- 813
+ 850
16384
postgres
@@ -4823,534 +4823,525 @@ false posixrules
standard public schema
1
- 813
- 2024-11-11.11:36:42
+ 850
+ 2024-11-15.10:47:44
2200
524
pg_database_owner
- 16696
- 808
+ 16792
+ 849
2
postgres
- 16666
- 808
+ 16762
+ 849
2
postgres
- 16652
- 808
+ 16748
+ 849
2
postgres
- 16681
- 808
+ 16777
+ 849
2
postgres
- 16711
- 808
+ 16807
+ 849
2
postgres
- 16659
- 808
+ 16755
+ 849
2
postgres
- 16647
- 807
+ 16743
+ 848
2
postgres
1
1
- 808
+ 849
uuid|0s
2950
1
2
- 808
+ 849
uuid|0s
2950
1
3
- 808
+ 849
uuid|0s
2950
-
- 1
+
4
- 808
+ 849
uuid|0s
2950
-
+
5
- 808
+ 849
uuid|0s
2950
-
- 1
- 6
- 808
- uuid|0s
- 2950
-
-
+
UserId1
- 16701
+ 16797
restrict
- 808
+ 849
1
- 16659
+ 16755
-
+
UserId2
- 16706
+ 16802
restrict
- 808
+ 849
1
- 16659
+ 16755
-
+
PinnedMessageId1
- 16737
- cascade
- 808
+ 16833
+ 849
1
- 16711
+ 16807
-
+
Id
- 16699
+ 16795
1
- 808
+ 849
1
403
-
+
UserId1
- 16729
- 808
+ 16825
+ 849
403
-
+
UserId2
- 16730
- 808
+ 16826
+ 849
403
-
+
PinnedMessageId1
- 16728
- 808
+ 16824
+ 849
403
-
- 16700
+
+ 16796
1
- 808
- 16699
+ 849
+ 16795
-
+
1
1
- 808
+ 849
uuid|0s
2950
-
+
1
2
- 808
+ 849
uuid|0s
2950
-
+
1
3
- 808
+ 849
uuid|0s
2950
-
+
1
4
- 808
+ 849
timestamp with time zone|0s
1184
-
+
1
5
- 808
+ 849
boolean|0s
16
-
+
ConferenceId
- 16671
- 808
+ 16767
+ 849
1
- 16652
+ 16748
-
+
MemberId
- 16676
- 808
+ 16772
+ 849
1
- 16659
+ 16755
-
+
Id
- 16669
+ 16765
1
- 808
+ 849
1
403
-
+
ConferenceId
- 16731
- 808
+ 16827
+ 849
403
-
+
MemberId
- 16732
- 808
+ 16828
+ 849
403
-
- 16670
+
+ 16766
1
- 808
- 16669
+ 849
+ 16765
-
+
1
1
- 808
+ 849
uuid|0s
2950
-
+
2
- 808
+ 849
text|0s
25
-
+
1
3
- 808
+ 849
integer|0s
23
-
+
4
- 808
+ 849
text|0s
25
-
+
Id
- 16657
+ 16753
1
- 808
+ 849
1
403
-
- 16658
+
+ 16754
1
- 808
- 16657
+ 849
+ 16753
-
+
1
1
- 808
+ 849
uuid|0s
2950
-
+
1
2
- 808
+ 849
uuid|0s
2950
-
+
1
3
- 808
+ 849
uuid|0s
2950
-
+
1
4
- 808
+ 849
timestamp with time zone|0s
1184
-
+
UserId
- 16691
+ 16787
cascade
- 808
+ 849
1
- 16659
+ 16755
-
+
ContactUserId
- 16686
+ 16782
restrict
- 808
+ 849
1
- 16659
+ 16755
-
+
Id
- 16684
+ 16780
1
- 808
+ 849
1
403
-
+
UserId
- 16734
- 808
+ 16830
+ 849
403
-
+
ContactUserId
- 16733
- 808
+ 16829
+ 849
403
-
- 16685
+
+ 16781
1
- 808
- 16684
+ 849
+ 16780
-
+
1
1
- 808
+ 849
uuid|0s
2950
-
+
1
2
- 808
+ 849
uuid|0s
2950
-
+
3
- 808
+ 849
uuid|0s
2950
-
+
4
- 808
+ 849
text|0s
25
-
+
5
- 808
+ 849
text|0s
25
-
+
6
- 808
+ 849
text|0s
25
-
+
1
7
- 808
+ 849
boolean|0s
16
-
+
1
8
- 808
+ 849
timestamp with time zone|0s
1184
-
+
ChatRoomId
- 16718
- 808
+ 16814
+ 849
1
- 16696
+ 16792
-
+
SenderId
- 16723
- 808
+ 16819
+ 849
1
- 16659
+ 16755
-
+
Id
- 16716
+ 16812
1
- 808
+ 849
1
403
-
+
ChatRoomId
- 16735
- 808
+ 16831
+ 849
403
-
+
SenderId
- 16736
- 808
+ 16832
+ 849
403
-
- 16717
+
+ 16813
1
- 808
- 16716
+ 849
+ 16812
-
+
1
1
- 808
+ 849
uuid|0s
2950
-
+
1
2
- 808
+ 849
varchar(100)|0s
1043
-
+
1
3
- 808
+ 849
varchar(100)|0s
1043
-
+
1
4
- 808
+ 849
text|0s
25
-
+
1
5
- 808
+ 849
text|0s
25
-
+
1
6
- 808
+ 849
text|0s
25
-
+
7
- 808
+ 849
text|0s
25
-
+
1
8
- 808
+ 849
timestamp with time zone|0s
1184
-
+
1
9
- 808
+ 849
timestamp with time zone|0s
1184
-
+
Id
- 16664
+ 16760
1
- 808
+ 849
1
403
-
- 16665
+
+ 16761
1
- 808
- 16664
+ 849
+ 16760
-
+
1
1
- 807
+ 848
varchar(150)|0s
1043
-
+
1
2
- 807
+ 848
varchar(32)|0s
1043
-
+
MigrationId
- 16650
+ 16746
1
- 807
+ 848
1
403
default
100
pg_catalog
-
- 16651
+
+ 16747
1
- 807
- 16650
+ 848
+ 16746
\ No newline at end of file
diff --git a/.idea/.idea.Eclipse/.idea/dataSources/30725304-56d3-46dd-a0f3-bd883f7c02e7/storage_v2/_src_/database/railway.Hf4GOg/schema/public.abK9xQ.meta b/.idea/.idea.Eclipse/.idea/dataSources/30725304-56d3-46dd-a0f3-bd883f7c02e7/storage_v2/_src_/database/railway.Hf4GOg/schema/public.abK9xQ.meta
index ba89736..7392c00 100644
--- a/.idea/.idea.Eclipse/.idea/dataSources/30725304-56d3-46dd-a0f3-bd883f7c02e7/storage_v2/_src_/database/railway.Hf4GOg/schema/public.abK9xQ.meta
+++ b/.idea/.idea.Eclipse/.idea/dataSources/30725304-56d3-46dd-a0f3-bd883f7c02e7/storage_v2/_src_/database/railway.Hf4GOg/schema/public.abK9xQ.meta
@@ -1,2 +1,2 @@
#n:public
-! [813, 0, null, null, -2147483648, -2147483648]
+! [850, 0, null, null, -2147483648, -2147483648]
diff --git a/.idea/.idea.Eclipse/.idea/workspace.xml b/.idea/.idea.Eclipse/.idea/workspace.xml
index 53c63c5..a2449ff 100644
--- a/.idea/.idea.Eclipse/.idea/workspace.xml
+++ b/.idea/.idea.Eclipse/.idea/workspace.xml
@@ -8,16 +8,15 @@
-
-
+
-
+
-
-
-
-
-
+
+
+
+
+
@@ -52,11 +51,16 @@
-
+
+
+
+
+
+
+
-
-
+
{
@@ -70,26 +74,26 @@
- {
+ "keyToString": {
+ ".NET Launch Settings Profile.Eclipse: http.executor": "Run",
+ "RunOnceActivity.ShowReadmeOnStart": "true",
+ "SONARLINT_PRECOMMIT_ANALYSIS": "true",
+ "git-widget-placeholder": "dev",
+ "node.js.detected.package.eslint": "true",
+ "node.js.detected.package.tslint": "true",
+ "node.js.selected.package.eslint": "(autodetect)",
+ "node.js.selected.package.tslint": "(autodetect)",
+ "nodejs_package_manager_path": "npm",
+ "settings.editor.selected.configurable": "preferences.pluginManager",
+ "vue.rearranger.settings.migration": "true"
},
- "keyToStringList": {
- "DatabaseDriversLRU": [
- "postgresql"
+ "keyToStringList": {
+ "DatabaseDriversLRU": [
+ "postgresql"
]
}
-}]]>
+}
@@ -212,78 +216,15 @@
-
-
-
- 1729587646779
-
-
-
- 1729587646779
-
-
-
- 1729587659735
-
-
-
- 1729587659735
-
-
-
- 1729593709434
-
-
-
- 1729593709434
-
-
-
- 1729594189136
-
-
-
- 1729594189136
-
-
-
- 1729594200513
-
-
-
- 1729594200513
-
-
-
- 1729594219693
-
-
-
- 1729594219693
-
-
-
- 1729594947433
-
-
-
- 1729594947433
-
-
-
- 1729595045096
-
-
-
- 1729595045096
-
-
-
- 1729957008554
-
-
-
- 1729957008554
+
+
+
+
+
+
+
+
+
@@ -605,7 +546,79 @@
1731475577671
-
+
+
+ 1731668029233
+
+
+
+ 1731668029233
+
+
+
+ 1731668050816
+
+
+
+ 1731668050816
+
+
+
+ 1731668611372
+
+
+
+ 1731668611372
+
+
+
+ 1731668621884
+
+
+
+ 1731668621884
+
+
+
+ 1731668629948
+
+
+
+ 1731668629948
+
+
+
+ 1731668655566
+
+
+
+ 1731668655566
+
+
+
+ 1731668672585
+
+
+
+ 1731668672585
+
+
+
+ 1731668697508
+
+
+
+ 1731668697508
+
+
+
+ 1731668704193
+
+
+
+ 1731668704193
+
+
@@ -615,13 +628,6 @@
-
-
-
-
-
-
-
@@ -630,7 +636,6 @@
-
@@ -640,7 +645,15 @@
-
+
+
+
+
+
+
+
+
+
@@ -658,19 +671,6 @@
-
- file://$PROJECT_DIR$/Eclipse/Middlewares/LastSeenUpdateMiddleware.cs
- 19
-
-
-
-
-
-
-
-
-
-
diff --git a/Eclipse/Controllers/ChatController.cs b/Eclipse/Controllers/ChatController.cs
new file mode 100644
index 0000000..01655be
--- /dev/null
+++ b/Eclipse/Controllers/ChatController.cs
@@ -0,0 +1,76 @@
+using System.Collections;
+using Eclipse.Exceptions;
+using Eclipse.Middlewares;
+using Eclipse.Models;
+using Eclipse.Models.Dto;
+using Eclipse.Repositories.Interfaces;
+using Microsoft.AspNetCore.Authorization;
+using Microsoft.AspNetCore.Mvc;
+using Minio.Exceptions;
+using Message = NuGet.Protocol.Plugins.Message;
+
+namespace Eclipse.Controllers;
+
+[ApiController]
+[Route("api/chat")]
+[Authorize]
+public class ChatRoomController : ControllerBase
+{
+ private readonly IChatRepository _chatRepository;
+
+ public ChatRoomController(IChatRepository chatRepository)
+ {
+ _chatRepository = chatRepository;
+ }
+
+ [HttpPost("create")]
+ public async Task> CreateChatRoom(Guid targetUserId)
+ {
+ try
+ {
+ var currentUserId = Guid.Parse(User.FindFirst("UserId").Value);
+ var chatRoom = await _chatRepository.CreateOrGetChatRoomAsync(currentUserId, targetUserId);
+ return new ApiResponse{ Message = "Success", Data = chatRoom };
+ }
+ catch (NotFoundException)
+ {
+ throw new NotFoundException("User");
+ }
+ }
+
+ [HttpGet("list")]
+ public async Task>> GetUserChatRooms()
+ {
+ var currentUserId = Guid.Parse(User.FindFirst("UserId").Value);
+ var chatRooms = await _chatRepository.GetUserChatRoomsAsync(currentUserId);
+
+ var chatRoomDtos = chatRooms.Select(cr => new ChatRoomDto
+ {
+ Id = cr.Id,
+ OtherUser = cr.UserId1 == currentUserId ? new UserDtoForChats(cr.User2) : new UserDtoForChats(cr.User1),
+ LastMessage = cr.Messages.FirstOrDefault(),
+ UnreadCount = cr.Messages.Count(m => !m.IsRead && m.SenderId != currentUserId)
+ });
+
+ return new ApiResponse> { Message = "Success", Data = chatRoomDtos };
+ }
+
+ [HttpGet("{chatRoomId}")]
+ public async Task> GetChatRoom(Guid chatRoomId)
+ {
+ var currentUserId = Guid.Parse(User.FindFirst("UserId").Value);
+
+ if (!await _chatRepository.ValidateChatRoomAccessAsync(chatRoomId, currentUserId))
+ {
+ throw new ForbiddenException();
+ }
+
+ var chatRoom = await _chatRepository.GetChatRoomAsync(chatRoomId);
+ if (chatRoom == null)
+ {
+ throw new NotFoundException("Room");
+ }
+
+ return new ApiResponse { Message = "Success", Data = chatRoom };
+ }
+}
\ No newline at end of file
diff --git a/Eclipse/Data/AutoMapper.cs b/Eclipse/Data/AutoMapper.cs
index 85b0b2b..35545ad 100644
--- a/Eclipse/Data/AutoMapper.cs
+++ b/Eclipse/Data/AutoMapper.cs
@@ -12,6 +12,8 @@ public AutoMapper()
CreateMap();
CreateMap();
CreateMap();
+ CreateMap();
CreateMap();
+ CreateMap();
}
}
\ No newline at end of file
diff --git a/Eclipse/Hubs/ChatHub.cs b/Eclipse/Hubs/ChatHub.cs
new file mode 100644
index 0000000..f569b91
--- /dev/null
+++ b/Eclipse/Hubs/ChatHub.cs
@@ -0,0 +1,218 @@
+using Eclipse.Models;
+using Eclipse.Repositories.Interfaces;
+using Microsoft.AspNetCore.Authorization;
+using Microsoft.AspNetCore.Cors;
+using Microsoft.AspNetCore.SignalR;
+
+namespace Eclipse.Hubs;
+
+[Authorize]
+[EnableCors("MyPolicy")]
+public class ChatHub : Hub
+{
+ private readonly IChatRepository _chatRepository;
+ private readonly ILogger _logger;
+
+ public ChatHub(IChatRepository chatRepository, ILogger logger)
+ {
+ _chatRepository = chatRepository;
+ _logger = logger;
+ }
+
+ public async Task SendMessage(Guid chatRoomId, string messageText, string? replyId = null)
+ {
+ var senderId = Guid.Parse(Context.User.FindFirst("UserId").Value);
+ var chatRoom = await _chatRepository.GetChatRoomAsync(chatRoomId);
+ var recipientId = chatRoom.UserId1 == senderId ? chatRoom.UserId2 : chatRoom.UserId1;
+
+ var message = new Message
+ {
+ Id = Guid.NewGuid(),
+ ChatRoomId = chatRoomId,
+ SenderId = senderId,
+ MessageText = messageText,
+ ReplyId = replyId,
+ IsRead = false,
+ Timestamp = DateTime.UtcNow
+ };
+
+ await _chatRepository.SaveMessageAsync(message);
+
+ var messageDto = MessageDto.FromMessage(message);
+
+ await Clients.User(recipientId.ToString()).SendAsync("NewMessage", messageDto);
+
+ _logger.LogInformation(
+ "Sent message {MessageId} in chat {ChatRoomId} from user {SenderId} to user {RecipientId}",
+ message.Id, chatRoomId, senderId, recipientId);
+ }
+
+ public async Task> GetChatHistory(Guid chatRoomId)
+ {
+ try
+ {
+ _logger.LogInformation($"GetChatHistory called for room {chatRoomId}");
+
+ var userId = Guid.Parse(Context.User.FindFirst("UserId").Value);
+ var chatRoom = await _chatRepository.GetChatRoomAsync(chatRoomId);
+
+ if (chatRoom == null || (chatRoom.UserId1 != userId && chatRoom.UserId2 != userId))
+ {
+ _logger.LogWarning($"Unauthorized access attempt to chat room {chatRoomId} by user {userId}");
+ throw new HubException("User is not part of the chat room.");
+ }
+
+ var messages = await _chatRepository.GetChatHistoryAsync(chatRoomId, 0, 10);
+ var messageDtos = messages.Select(MessageDto.FromMessage).ToList();
+
+ _logger.LogInformation($"Retrieved {messageDtos.Count()} messages for chat room {chatRoomId}");
+
+ return messageDtos;
+ }
+ catch (Exception ex)
+ {
+ _logger.LogError($"Error in GetChatHistory: {ex.Message}");
+ throw new HubException($"Failed to get chat history: {ex.Message}");
+ }
+ }
+
+ public async Task ReceiveMessage(Guid chatRoomId, Guid messageId)
+ {
+ var userId = Guid.Parse(Context.User.FindFirst("UserId").Value);
+
+ var message = await _chatRepository.GetMessageAsync(messageId);
+ if (message == null)
+ {
+ throw new ArgumentException("Message not found", nameof(messageId));
+ }
+
+ var chatRoom = await _chatRepository.GetChatRoomAsync(chatRoomId);
+ if (chatRoom == null || (chatRoom.UserId1 != userId && chatRoom.UserId2 != userId))
+ {
+ throw new UnauthorizedAccessException("User is not part of the chat room.");
+ }
+
+ _logger.LogInformation("Message {MessageId} in chat room {ChatRoomId} was received by user {UserId}",
+ messageId, chatRoomId, userId);
+
+ // Уведомление отправителя, что сообщение было получено
+ await Clients.User(message.SenderId.ToString()).SendAsync("MessageReceived", new
+ {
+ ChatRoomId = chatRoomId,
+ MessageId = messageId,
+ ReceiverId = userId,
+ Timestamp = DateTime.UtcNow
+ });
+ }
+
+
+ public async Task MarkAsRead(Guid messageId)
+ {
+ var message = await _chatRepository.GetMessageAsync(messageId);
+ await _chatRepository.MarkMessageAsReadAsync(messageId);
+ await Clients.User(message.SenderId.ToString()).SendAsync("MessageRead", messageId);
+ }
+
+ public async Task AddReaction(Guid messageId, string reactionId)
+ {
+ var userId = Guid.Parse(Context.User.FindFirst("UserId").Value);
+ var message = await _chatRepository.GetMessageAsync(messageId);
+ await _chatRepository.AddReactionAsync(messageId, reactionId);
+ await Clients.Users(new[] { message.SenderId.ToString(), userId.ToString() })
+ .SendAsync("ReactionAdded", messageId, reactionId);
+ }
+
+ public async Task PinMessage(Guid chatRoomId, Guid messageId)
+ {
+ var chatRoom = await _chatRepository.GetChatRoomAsync(chatRoomId);
+ await _chatRepository.PinMessageAsync(chatRoomId, messageId);
+ await Clients.Users(new[] { chatRoom.UserId1.ToString(), chatRoom.UserId2.ToString() })
+ .SendAsync("MessagePinned", chatRoomId, messageId);
+ }
+
+ public override async Task OnConnectedAsync()
+ {
+ try
+ {
+ var currentUserId = Guid.Parse(Context.User.FindFirst("UserId").Value);
+ var secondUserIdStr = Context.GetHttpContext().Request.Query["userId"].ToString();
+ if (string.IsNullOrEmpty(secondUserIdStr) || !Guid.TryParse(secondUserIdStr, out Guid secondUserId))
+ {
+ throw new ArgumentException("Invalid second user ID format");
+ }
+
+ var chatRoom = await _chatRepository.CreateOrGetChatRoomAsync(currentUserId, secondUserId);
+ if (chatRoom == null)
+ {
+ throw new ArgumentException("Chat room not found for these users");
+ }
+
+ await Groups.AddToGroupAsync(Context.ConnectionId, chatRoom.Id.ToString());
+ var otherUserId = chatRoom.UserId1 == currentUserId ? chatRoom.UserId2 : chatRoom.UserId1;
+ await Clients.User(otherUserId.ToString()).SendAsync("UserConnected", new
+ {
+ UserId = currentUserId,
+ RoomId = chatRoom.Id,
+ Timestamp = DateTime.UtcNow
+ });
+
+ _logger.LogInformation($"User {currentUserId} connected to chat room {chatRoom.Id}");
+ }
+ catch (Exception ex)
+ {
+ _logger.LogError($"Error during connection: {ex.Message}");
+ Context.Abort();
+ }
+ }
+
+ public override async Task OnDisconnectedAsync(Exception? exception)
+ {
+ try
+ {
+ var currentUserId = Guid.Parse(Context.User.FindFirst("UserId").Value);
+ var secondUserIdStr = Context.GetHttpContext().Request.Query["secondUserId"].ToString();
+ if (!string.IsNullOrEmpty(secondUserIdStr) && Guid.TryParse(secondUserIdStr, out Guid secondUserId))
+ {
+ var chatRoom = await _chatRepository.CreateOrGetChatRoomAsync(currentUserId, secondUserId);
+ if (chatRoom != null)
+ {
+ await Groups.RemoveFromGroupAsync(Context.ConnectionId, chatRoom.Id.ToString());
+ var otherUserId = chatRoom.UserId1 == currentUserId ? chatRoom.UserId2 : chatRoom.UserId1;
+ await Clients.User(otherUserId.ToString()).SendAsync("UserDisconnected", new
+ {
+ UserId = currentUserId,
+ RoomId = chatRoom.Id,
+ Timestamp = DateTime.UtcNow
+ });
+
+ _logger.LogInformation($"User {currentUserId} disconnected from chat room {chatRoom.Id}");
+ }
+ }
+ }
+ catch (Exception ex)
+ {
+ _logger.LogError($"Error during disconnection: {ex.Message}");
+ }
+ finally
+ {
+ await base.OnDisconnectedAsync(exception);
+ }
+ }
+
+ public async Task JoinChatRoom(string chatRoomId)
+ {
+ if (string.IsNullOrWhiteSpace(chatRoomId))
+ {
+ throw new ArgumentException("ChatRoomId cannot be null or empty", nameof(chatRoomId));
+ }
+
+ // Подключение текущего пользователя к группе
+ await Groups.AddToGroupAsync(Context.ConnectionId, chatRoomId);
+
+ // Уведомление других участников комнаты о новом пользователе
+ await Clients.Group(chatRoomId).SendAsync("UserJoined", Context.ConnectionId, chatRoomId);
+
+ // Логирование
+ Console.WriteLine($"User {Context.ConnectionId} joined chat room {chatRoomId}");
+ }
+}
\ No newline at end of file
diff --git a/Eclipse/Middlewares/ErrorHandlingMiddleware.cs b/Eclipse/Middlewares/ErrorHandlingMiddleware.cs
index c0e1689..8c4a0b5 100644
--- a/Eclipse/Middlewares/ErrorHandlingMiddleware.cs
+++ b/Eclipse/Middlewares/ErrorHandlingMiddleware.cs
@@ -1,4 +1,5 @@
using Eclipse.Exceptions;
+using Minio.Exceptions;
namespace Eclipse.Middlewares;
@@ -35,6 +36,12 @@ await context.Response.WriteAsJsonAsync(new ApiResponse