From ea25e3b63dd8d98df288017c41ad145f99b88ba8 Mon Sep 17 00:00:00 2001 From: hz Date: Thu, 5 Jun 2025 09:06:32 -0500 Subject: [PATCH] Adding chat system --- Controllers/ChatController.cs | 47 +++++++++++++++++++ GameAuthApi.Tests/ChatControllerTests.cs | 58 ++++++++++++++++++++++++ Models/ChatMessage.cs | 19 ++++++++ Models/DTOs/ChatMessageRequest.cs | 6 +++ Program.cs | 1 + Services/ChatService.cs | 28 ++++++++++++ 6 files changed, 159 insertions(+) create mode 100644 Controllers/ChatController.cs create mode 100644 GameAuthApi.Tests/ChatControllerTests.cs create mode 100644 Models/ChatMessage.cs create mode 100644 Models/DTOs/ChatMessageRequest.cs create mode 100644 Services/ChatService.cs diff --git a/Controllers/ChatController.cs b/Controllers/ChatController.cs new file mode 100644 index 0000000..955e4b8 --- /dev/null +++ b/Controllers/ChatController.cs @@ -0,0 +1,47 @@ +using GameAuthApi.Models; +using GameAuthApi.Services; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Mvc; +using System.Security.Claims; +using System.Threading.Tasks; + +namespace GameAuthApi.Controllers; + +[ApiController] +[Route("api/[controller]")] +public class ChatController : ControllerBase +{ + private readonly ChatService _chatService; + + public ChatController(ChatService chatService) + { + _chatService = chatService; + } + + [HttpPost("send")] + [Authorize(Roles = "USER,SUPER")] + public async Task Send([FromBody] ChatMessageRequest req) + { + if (string.IsNullOrWhiteSpace(req.Content)) + return BadRequest("Content required"); + + var senderId = User.FindFirstValue(ClaimTypes.NameIdentifier); + var msg = new ChatMessage + { + SenderId = senderId, + Content = req.Content + }; + await _chatService.CreateAsync(msg); + return Ok(msg); + } + + [HttpGet("recent")] + [Authorize(Roles = "USER,SUPER")] + public async Task Recent([FromQuery] int count = 10) + { + if (count <= 0) + count = 10; + var messages = await _chatService.GetRecentAsync(count); + return Ok(messages); + } +} diff --git a/GameAuthApi.Tests/ChatControllerTests.cs b/GameAuthApi.Tests/ChatControllerTests.cs new file mode 100644 index 0000000..e6c8c50 --- /dev/null +++ b/GameAuthApi.Tests/ChatControllerTests.cs @@ -0,0 +1,58 @@ +using GameAuthApi.Controllers; +using GameAuthApi.Models; +using GameAuthApi.Services; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; +using Microsoft.Extensions.Configuration; +using System.Security.Claims; + +public class ChatControllerTests +{ + private static IConfiguration cfg = new ConfigurationBuilder() + .AddInMemoryCollection(new Dictionary + { + {"MongoDB:ConnectionString","mongodb://localhost"}, + {"MongoDB:DatabaseName","test"} + }).Build(); + + private static ControllerContext ContextWithUser(string id) + { + var ctx = new DefaultHttpContext(); + ctx.User = new ClaimsPrincipal(new ClaimsIdentity(new[] + { + new Claim(ClaimTypes.NameIdentifier,id) + }, "mock")); + return new ControllerContext { HttpContext = ctx }; + } + + [Fact] + public async Task Send_EmptyContent_ReturnsBadRequest() + { + var svc = new Mock(cfg); + var controller = new ChatController(svc.Object) + { + ControllerContext = ContextWithUser("u") + }; + + var result = await controller.Send(new ChatMessageRequest { Content = "" }); + + Assert.IsType(result); + } + + [Fact] + public async Task Recent_ReturnsOk() + { + var list = new List(); + var svc = new Mock(cfg); + svc.Setup(s => s.GetRecentAsync(5)).ReturnsAsync(list); + var controller = new ChatController(svc.Object) + { + ControllerContext = ContextWithUser("u") + }; + + var result = await controller.Recent(5); + + var ok = Assert.IsType(result); + Assert.Equal(list, ok.Value); + } +} diff --git a/Models/ChatMessage.cs b/Models/ChatMessage.cs new file mode 100644 index 0000000..a2ef9f4 --- /dev/null +++ b/Models/ChatMessage.cs @@ -0,0 +1,19 @@ +using System; +using MongoDB.Bson; +using MongoDB.Bson.Serialization.Attributes; + +namespace GameAuthApi.Models; + +public class ChatMessage +{ + [BsonId] + [BsonRepresentation(BsonType.ObjectId)] + public string Id { get; set; } + + [BsonRepresentation(BsonType.ObjectId)] + public string SenderId { get; set; } + + public string Content { get; set; } + + public DateTime Timestamp { get; set; } = DateTime.UtcNow; +} diff --git a/Models/DTOs/ChatMessageRequest.cs b/Models/DTOs/ChatMessageRequest.cs new file mode 100644 index 0000000..68ec12c --- /dev/null +++ b/Models/DTOs/ChatMessageRequest.cs @@ -0,0 +1,6 @@ +namespace GameAuthApi.Models; + +public class ChatMessageRequest +{ + public string Content { get; set; } +} diff --git a/Program.cs b/Program.cs index 24e309b..6edfeca 100644 --- a/Program.cs +++ b/Program.cs @@ -53,6 +53,7 @@ builder.Services.AddSingleton(); builder.Services.AddSingleton(); builder.Services.AddSingleton(); builder.Services.AddSingleton(); +builder.Services.AddSingleton(); var jwtKey = builder.Configuration["Jwt:Key"]; var keyBytes = Encoding.UTF8.GetBytes(jwtKey); diff --git a/Services/ChatService.cs b/Services/ChatService.cs new file mode 100644 index 0000000..0e32105 --- /dev/null +++ b/Services/ChatService.cs @@ -0,0 +1,28 @@ +using System.Collections.Generic; +using System.Threading.Tasks; +using GameAuthApi.Models; +using Microsoft.Extensions.Configuration; +using MongoDB.Driver; + +namespace GameAuthApi.Services; + +public class ChatService +{ + private readonly IMongoCollection _messages; + + public ChatService(IConfiguration config) + { + var client = new MongoClient(config["MongoDB:ConnectionString"]); + var db = client.GetDatabase(config["MongoDB:DatabaseName"]); + _messages = db.GetCollection("ChatMessages"); + } + + public virtual async Task CreateAsync(ChatMessage msg) => + await _messages.InsertOneAsync(msg); + + public virtual async Task> GetRecentAsync(int count) => + await _messages.Find(_ => true) + .SortByDescending(m => m.Timestamp) + .Limit(count) + .ToListAsync(); +}