From 503ee1ba4f7fe788d095ff8e8478eaada575f82e Mon Sep 17 00:00:00 2001 From: Laki27 Date: Sat, 4 Apr 2026 15:22:59 +0200 Subject: [PATCH 1/4] feat: initial chat service implementation --- src/Services/.gitignore => .gitignore | 0 JobHubMATF.slnx | 24 +++++ src/Services/Chat.API/Chat.API.csproj | 15 +++ src/Services/Chat.API/Chat.API.http | 6 ++ .../Controllers/MessagesController.cs | 44 +++++++++ src/Services/Chat.API/Models/Chat.cs | 21 ++++ src/Services/Chat.API/Models/Message.cs | 24 +++++ .../Chat.API/Models/MongoDbSettings.cs | 8 ++ .../Chat.API/Models/SendMessageRequest.cs | 10 ++ src/Services/Chat.API/Program.cs | 23 +++++ .../Chat.API/Properties/launchSettings.json | 23 +++++ src/Services/Chat.API/Services/ChatService.cs | 99 +++++++++++++++++++ .../Chat.API/appsettings.Development.json | 8 ++ src/Services/Chat.API/appsettings.json | 14 +++ 14 files changed, 319 insertions(+) rename src/Services/.gitignore => .gitignore (100%) create mode 100644 JobHubMATF.slnx create mode 100644 src/Services/Chat.API/Chat.API.csproj create mode 100644 src/Services/Chat.API/Chat.API.http create mode 100644 src/Services/Chat.API/Controllers/MessagesController.cs create mode 100644 src/Services/Chat.API/Models/Chat.cs create mode 100644 src/Services/Chat.API/Models/Message.cs create mode 100644 src/Services/Chat.API/Models/MongoDbSettings.cs create mode 100644 src/Services/Chat.API/Models/SendMessageRequest.cs create mode 100644 src/Services/Chat.API/Program.cs create mode 100644 src/Services/Chat.API/Properties/launchSettings.json create mode 100644 src/Services/Chat.API/Services/ChatService.cs create mode 100644 src/Services/Chat.API/appsettings.Development.json create mode 100644 src/Services/Chat.API/appsettings.json diff --git a/src/Services/.gitignore b/.gitignore similarity index 100% rename from src/Services/.gitignore rename to .gitignore diff --git a/JobHubMATF.slnx b/JobHubMATF.slnx new file mode 100644 index 0000000..8e892f9 --- /dev/null +++ b/JobHubMATF.slnx @@ -0,0 +1,24 @@ + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/Services/Chat.API/Chat.API.csproj b/src/Services/Chat.API/Chat.API.csproj new file mode 100644 index 0000000..ad5d695 --- /dev/null +++ b/src/Services/Chat.API/Chat.API.csproj @@ -0,0 +1,15 @@ + + + + net10.0 + enable + enable + + + + + + + + + diff --git a/src/Services/Chat.API/Chat.API.http b/src/Services/Chat.API/Chat.API.http new file mode 100644 index 0000000..e63eb66 --- /dev/null +++ b/src/Services/Chat.API/Chat.API.http @@ -0,0 +1,6 @@ +@Chat.API_HostAddress = http://localhost:5075 + +GET {{Chat.API_HostAddress}}/weatherforecast/ +Accept: application/json + +### diff --git a/src/Services/Chat.API/Controllers/MessagesController.cs b/src/Services/Chat.API/Controllers/MessagesController.cs new file mode 100644 index 0000000..73cf251 --- /dev/null +++ b/src/Services/Chat.API/Controllers/MessagesController.cs @@ -0,0 +1,44 @@ + +using Chat.API.Models; +using Chat.API.Services; +using Microsoft.AspNetCore.Mvc; + +namespace Chat.API.Controllers +{ + [ApiController] + [Route("api/[controller]")] + public class MessagesController : ControllerBase + { + private readonly ChatService _chatService; + + public MessagesController(ChatService chatService) + { + _chatService = chatService; + } + [HttpPost] + public async Task SendMessage([FromBody] SendMessageRequest request) { + if (string.IsNullOrEmpty(request.Text)) + return BadRequest("Message cannot be empty"); + + var message = await _chatService.SendMessageAsync( + request.SenderId, + request.ReciverId, + request.Text + ); + + return Ok(message); + + } + + [HttpGet] + public async Task GetMessages([FromQuery] string user1, [FromQuery] string user2) + { + if (string.IsNullOrEmpty(user1) || string.IsNullOrEmpty(user2)) + return BadRequest("User IDs are required"); + + var messages = await _chatService.GetMessagesAsync(user1, user2); + + return Ok(messages); + } + } +} diff --git a/src/Services/Chat.API/Models/Chat.cs b/src/Services/Chat.API/Models/Chat.cs new file mode 100644 index 0000000..b06ce13 --- /dev/null +++ b/src/Services/Chat.API/Models/Chat.cs @@ -0,0 +1,21 @@ +using MongoDB.Bson; +using MongoDB.Bson.Serialization.Attributes; + + +namespace Chat.API.Models +{ + + public class Chat + { + [BsonId] + [BsonRepresentation(BsonType.ObjectId)] + + public string Id { get; set; } + + public string User1Id { get; set; } + + public string User2Id { get; set; } + + public DateTime CreatedAt { get; set; } + } +} \ No newline at end of file diff --git a/src/Services/Chat.API/Models/Message.cs b/src/Services/Chat.API/Models/Message.cs new file mode 100644 index 0000000..daf4a7a --- /dev/null +++ b/src/Services/Chat.API/Models/Message.cs @@ -0,0 +1,24 @@ + +using MongoDB.Bson; +using MongoDB.Bson.Serialization.Attributes; + +namespace Chat.API.Models +{ + + public class Message + { + [BsonId] + [BsonRepresentation(BsonType.ObjectId)] + public string Id { get; set; } + + public string ChatId { get; set; } + + public string SenderId { get; set; } + + public string Text { get; set; } + + public DateTime Timestamp { get; set; } + + } + +} \ No newline at end of file diff --git a/src/Services/Chat.API/Models/MongoDbSettings.cs b/src/Services/Chat.API/Models/MongoDbSettings.cs new file mode 100644 index 0000000..c337c68 --- /dev/null +++ b/src/Services/Chat.API/Models/MongoDbSettings.cs @@ -0,0 +1,8 @@ +namespace Chat.API.Models +{ + public class MongoDbSettings + { + public string ConnectionString { get; set; } + public string DatabaseName { get; set; } + } +} diff --git a/src/Services/Chat.API/Models/SendMessageRequest.cs b/src/Services/Chat.API/Models/SendMessageRequest.cs new file mode 100644 index 0000000..c96d8ed --- /dev/null +++ b/src/Services/Chat.API/Models/SendMessageRequest.cs @@ -0,0 +1,10 @@ +namespace Chat.API.Models +{ + public class SendMessageRequest + { + public string SenderId { get; set; } + public string ReciverId { get; set; } + + public string Text { get; set; } + } +} diff --git a/src/Services/Chat.API/Program.cs b/src/Services/Chat.API/Program.cs new file mode 100644 index 0000000..8c06e3b --- /dev/null +++ b/src/Services/Chat.API/Program.cs @@ -0,0 +1,23 @@ +using Chat.API.Services; +using Scalar.AspNetCore; + +var builder = WebApplication.CreateBuilder(args); + +// Add services to the container. + +builder.Services.AddControllers(); +// Learn more about configuring OpenAPI at https://aka.ms/aspnet/openapi +builder.Services.AddOpenApi(); +builder.Services.AddSingleton(); +var app = builder.Build(); + +// Configure the HTTP request pipeline. +if (app.Environment.IsDevelopment()) +{ + app.MapOpenApi(); + app.MapScalarApiReference(); +} + +app.MapControllers(); + +app.Run(); diff --git a/src/Services/Chat.API/Properties/launchSettings.json b/src/Services/Chat.API/Properties/launchSettings.json new file mode 100644 index 0000000..f9ab1e8 --- /dev/null +++ b/src/Services/Chat.API/Properties/launchSettings.json @@ -0,0 +1,23 @@ +{ + "$schema": "https://json.schemastore.org/launchsettings.json", + "profiles": { + "http": { + "commandName": "Project", + "dotnetRunMessages": true, + "launchBrowser": false, + "applicationUrl": "http://localhost:5075", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + }, + "https": { + "commandName": "Project", + "dotnetRunMessages": true, + "launchBrowser": false, + "applicationUrl": "https://localhost:7175;http://localhost:5075", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + } + } +} diff --git a/src/Services/Chat.API/Services/ChatService.cs b/src/Services/Chat.API/Services/ChatService.cs new file mode 100644 index 0000000..b34a3ae --- /dev/null +++ b/src/Services/Chat.API/Services/ChatService.cs @@ -0,0 +1,99 @@ +using Chat.API.Models; +using MongoDB.Driver; + + + +namespace Chat.API.Services +{ + public class ChatService + { + private readonly IMongoCollection _messages; + private readonly IMongoCollection _chats; + + public ChatService(IConfiguration config) + { + var settings = config.GetSection("MongoDbSettings").Get(); + var client = new MongoClient(settings.ConnectionString); + var database = client.GetDatabase(settings.DatabaseName); + + _chats = database.GetCollection("Chats"); + _messages = database.GetCollection("Messages"); + } + + public async Task GetOrCreateChatAsync(string user1, string user2) + { + // Sortiranje userId-eva + var sortedUsers = new List { user1, user2 }; + sortedUsers.Sort(); + + var u1 = sortedUsers[0]; + var u2 = sortedUsers[1]; + + // Traženje postojećeg chata + var chat = await _chats + .Find(c => c.User1Id == u1 && c.User2Id == u2) + .FirstOrDefaultAsync(); + + if (chat != null) + return chat; + + // Kreiranje novog chata + var newChat = new Models.Chat + { + User1Id = u1, + User2Id = u2, + CreatedAt = DateTime.UtcNow + }; + + await _chats.InsertOneAsync(newChat); + + return newChat; + } + + public async Task SendMessageAsync(string senderId, string receiverId, string text) + { + // 1. Uzmi ili kreiraj chat + var chat = await GetOrCreateChatAsync(senderId, receiverId); + + // 2. Kreiraj poruku + var message = new Message + { + ChatId = chat.Id, + SenderId = senderId, + Text = text, + Timestamp = DateTime.UtcNow + }; + + // 3. Sačuvaj u bazu + await _messages.InsertOneAsync(message); + + return message; + } + + public async Task> GetMessagesAsync(string user1, string user2) + { + // 1. Sortiranje userId-eva + var sortedUsers = new List { user1, user2 }; + sortedUsers.Sort(); + + var u1 = sortedUsers[0]; + var u2 = sortedUsers[1]; + + // 2. Pronađi chat + var chat = await _chats + .Find(c => c.User1Id == u1 && c.User2Id == u2) + .FirstOrDefaultAsync(); + + if (chat == null) + return new List(); + + // 3. Uzmi sve poruke za taj chat + var messages = await _messages + .Find(m => m.ChatId == chat.Id) + .SortBy(m => m.Timestamp) + .ToListAsync(); + + return messages; + } + } +} diff --git a/src/Services/Chat.API/appsettings.Development.json b/src/Services/Chat.API/appsettings.Development.json new file mode 100644 index 0000000..0c208ae --- /dev/null +++ b/src/Services/Chat.API/appsettings.Development.json @@ -0,0 +1,8 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + } +} diff --git a/src/Services/Chat.API/appsettings.json b/src/Services/Chat.API/appsettings.json new file mode 100644 index 0000000..e15d651 --- /dev/null +++ b/src/Services/Chat.API/appsettings.json @@ -0,0 +1,14 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + }, + "AllowedHosts": "*", + + "MongoDbSettings": { + "ConnectionString": "mongodb://localhost:27017", + "DatabaseName": "ChatDb" + } +} \ No newline at end of file From 9aa952e06ec4a21bc11db60b0e0fd0e0ca24ca67 Mon Sep 17 00:00:00 2001 From: Laki27 Date: Wed, 15 Apr 2026 21:43:45 +0200 Subject: [PATCH 2/4] Real-time chat backend --- src/Services/Chat.API/Chat.API.csproj | 1 + src/Services/Chat.API/Hubs/ChatHub.cs | 23 +++++++++++++++++++++++ src/Services/Chat.API/Program.cs | 4 +++- 3 files changed, 27 insertions(+), 1 deletion(-) create mode 100644 src/Services/Chat.API/Hubs/ChatHub.cs diff --git a/src/Services/Chat.API/Chat.API.csproj b/src/Services/Chat.API/Chat.API.csproj index ad5d695..e64980f 100644 --- a/src/Services/Chat.API/Chat.API.csproj +++ b/src/Services/Chat.API/Chat.API.csproj @@ -8,6 +8,7 @@ + diff --git a/src/Services/Chat.API/Hubs/ChatHub.cs b/src/Services/Chat.API/Hubs/ChatHub.cs new file mode 100644 index 0000000..c1a848d --- /dev/null +++ b/src/Services/Chat.API/Hubs/ChatHub.cs @@ -0,0 +1,23 @@ +using Chat.API.Services; +using Microsoft.AspNetCore.SignalR; +namespace Chat.API.Hubs +{ + public class ChatHub : Hub + { + private readonly ChatService _chatService; + + public ChatHub(ChatService chatService) + { + _chatService = chatService; + } + public async Task SendMessage(string senderId, string reciverId, string message) + { + var savedMessage = await _chatService.SendMessageAsync(senderId, reciverId, message); + await Clients.All.SendAsync("ReceiveMessage", + savedMessage.SenderId, + reciverId, + savedMessage.Text, + savedMessage.Timestamp); + } + } +} diff --git a/src/Services/Chat.API/Program.cs b/src/Services/Chat.API/Program.cs index 8c06e3b..7bbc463 100644 --- a/src/Services/Chat.API/Program.cs +++ b/src/Services/Chat.API/Program.cs @@ -1,6 +1,6 @@ using Chat.API.Services; using Scalar.AspNetCore; - +using Chat.API.Hubs; var builder = WebApplication.CreateBuilder(args); // Add services to the container. @@ -9,6 +9,7 @@ // Learn more about configuring OpenAPI at https://aka.ms/aspnet/openapi builder.Services.AddOpenApi(); builder.Services.AddSingleton(); +builder.Services.AddSignalR(); var app = builder.Build(); // Configure the HTTP request pipeline. @@ -19,5 +20,6 @@ } app.MapControllers(); +app.MapHub("/chatHub"); app.Run(); From 22142b399dad9cfd5df2259729428d03a4a5f35f Mon Sep 17 00:00:00 2001 From: Laki27 Date: Fri, 17 Apr 2026 00:46:34 +0200 Subject: [PATCH 3/4] mini-test-siganlR --- src/Services/Chat.API/Program.cs | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/src/Services/Chat.API/Program.cs b/src/Services/Chat.API/Program.cs index 7bbc463..18775df 100644 --- a/src/Services/Chat.API/Program.cs +++ b/src/Services/Chat.API/Program.cs @@ -10,6 +10,21 @@ builder.Services.AddOpenApi(); builder.Services.AddSingleton(); builder.Services.AddSignalR(); + +builder.Services.AddCors(options => +{ + options.AddPolicy("AllowAll", + policy => + { + policy.WithOrigins("http://127.0.0.1:5500", "null") + .AllowAnyHeader() + .AllowAnyMethod() + .AllowCredentials(); + }); +}); + + + var app = builder.Build(); // Configure the HTTP request pipeline. @@ -19,6 +34,10 @@ app.MapScalarApiReference(); } +app.UseCors("AllowAll"); + +app.UseAuthorization(); + app.MapControllers(); app.MapHub("/chatHub"); From 7516e7791d1273bd6660e44af38708ec6bf6ce63 Mon Sep 17 00:00:00 2001 From: Laki27 Date: Fri, 17 Apr 2026 23:42:09 +0200 Subject: [PATCH 4/4] Poruke idu samo jednom korisniku --- src/Services/Chat.API/Hubs/ChatHub.cs | 38 ++++++++++++++++++++++++++- 1 file changed, 37 insertions(+), 1 deletion(-) diff --git a/src/Services/Chat.API/Hubs/ChatHub.cs b/src/Services/Chat.API/Hubs/ChatHub.cs index c1a848d..52e1406 100644 --- a/src/Services/Chat.API/Hubs/ChatHub.cs +++ b/src/Services/Chat.API/Hubs/ChatHub.cs @@ -5,6 +5,7 @@ namespace Chat.API.Hubs public class ChatHub : Hub { private readonly ChatService _chatService; + private static readonly Dictionary _connections = new(); public ChatHub(ChatService chatService) { @@ -13,11 +14,46 @@ public ChatHub(ChatService chatService) public async Task SendMessage(string senderId, string reciverId, string message) { var savedMessage = await _chatService.SendMessageAsync(senderId, reciverId, message); - await Clients.All.SendAsync("ReceiveMessage", + if(_connections.TryGetValue(reciverId, out var connectionId)) + { + await Clients.Client(connectionId).SendAsync("ReceiveMessage", savedMessage.SenderId, reciverId, savedMessage.Text, savedMessage.Timestamp); + } + + if (_connections.TryGetValue(senderId, out var senderConnection)) + { + await Clients.Client(senderConnection).SendAsync("ReceiveMessage", + savedMessage.SenderId, + reciverId, + savedMessage.Text, + savedMessage.Timestamp); + } + } + public override Task OnConnectedAsync() + { + var userId = Context.GetHttpContext().Request.Query["userId"]; + + if (!string.IsNullOrEmpty(userId)) + { + _connections[userId] = Context.ConnectionId; + } + + return base.OnConnectedAsync(); + } + + public override Task OnDisconnectedAsync(Exception exception) + { + var item = _connections.FirstOrDefault(x => x.Value == Context.ConnectionId); + + if (!string.IsNullOrEmpty(item.Key)) + { + _connections.Remove(item.Key); + } + + return base.OnDisconnectedAsync(exception); } } }