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..e64980f
--- /dev/null
+++ b/src/Services/Chat.API/Chat.API.csproj
@@ -0,0 +1,16 @@
+
+
+
+ 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/Hubs/ChatHub.cs b/src/Services/Chat.API/Hubs/ChatHub.cs
new file mode 100644
index 0000000..52e1406
--- /dev/null
+++ b/src/Services/Chat.API/Hubs/ChatHub.cs
@@ -0,0 +1,59 @@
+using Chat.API.Services;
+using Microsoft.AspNetCore.SignalR;
+namespace Chat.API.Hubs
+{
+ public class ChatHub : Hub
+ {
+ private readonly ChatService _chatService;
+ private static readonly Dictionary _connections = new();
+
+ public ChatHub(ChatService chatService)
+ {
+ _chatService = chatService;
+ }
+ public async Task SendMessage(string senderId, string reciverId, string message)
+ {
+ var savedMessage = await _chatService.SendMessageAsync(senderId, reciverId, message);
+ 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);
+ }
+ }
+}
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..18775df
--- /dev/null
+++ b/src/Services/Chat.API/Program.cs
@@ -0,0 +1,44 @@
+using Chat.API.Services;
+using Scalar.AspNetCore;
+using Chat.API.Hubs;
+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();
+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.
+if (app.Environment.IsDevelopment())
+{
+ app.MapOpenApi();
+ app.MapScalarApiReference();
+}
+
+app.UseCors("AllowAll");
+
+app.UseAuthorization();
+
+app.MapControllers();
+app.MapHub("/chatHub");
+
+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