diff --git a/Govor.API.Tests/IntegrationTests/EF/Repositories/UsersRepositoryTests.cs b/Govor.API.Tests/IntegrationTests/EF/Repositories/UsersRepositoryTests.cs index 80f997a..ef8b480 100644 --- a/Govor.API.Tests/IntegrationTests/EF/Repositories/UsersRepositoryTests.cs +++ b/Govor.API.Tests/IntegrationTests/EF/Repositories/UsersRepositoryTests.cs @@ -39,6 +39,10 @@ public class UsersRepositoryTests var random = new Random(); var users = _fixture.CreateMany(random.Next(2, 10)).ToList(); + _options = new DbContextOptionsBuilder() + .UseInMemoryDatabase(databaseName: "DbGovor_users_getall") + .Options; + await using var context = new GovorDbContext(_options); var userRepository = new UsersRepository(context, _userValidator); diff --git a/Govor.API.Tests/UnitTests/Services/AuthServiceTests.cs b/Govor.API.Tests/UnitTests/Services/AuthServiceTests.cs index b67e805..ce4a958 100644 --- a/Govor.API.Tests/UnitTests/Services/AuthServiceTests.cs +++ b/Govor.API.Tests/UnitTests/Services/AuthServiceTests.cs @@ -3,7 +3,7 @@ using Govor.API.Services.Authentication; using Govor.Core.Infrastructure.Extensions; using Govor.Core.Models; using Govor.Core.Repositories.Users; -using Govor.Core.Services; +using Govor.API.Services.Authentication.Interfaces; using Moq; namespace Govor.API.Tests.UnitTests.Services; diff --git a/Govor.API.Tests/UnitTests/Services/LocalStorageServiceTests.cs b/Govor.API.Tests/UnitTests/Services/LocalStorageServiceTests.cs new file mode 100644 index 0000000..6ec0e00 --- /dev/null +++ b/Govor.API.Tests/UnitTests/Services/LocalStorageServiceTests.cs @@ -0,0 +1,78 @@ +using AutoFixture; +using Govor.API.Services; +using Microsoft.AspNetCore.Hosting; +using Moq; + +namespace Govor.API.Tests.UnitTests.Services; + +[TestFixture] +public class LocalStorageServiceTests +{ + private Fixture _fixture; + private IStorageService _service; + private Mock _webHostEnvironmentMock; + private string _tempDirectory; + + [SetUp] + public void SetUp() + { + _fixture = new Fixture(); + _webHostEnvironmentMock = new Mock(); + + _tempDirectory = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString()); + Directory.CreateDirectory(_tempDirectory); + + _webHostEnvironmentMock.Setup(w => w.ContentRootPath).Returns(_tempDirectory); + + _service = new LocalStorageService(_webHostEnvironmentMock.Object); + } + + [TearDown] + public void TearDown() + { + if (Directory.Exists(_tempDirectory)) + { + Directory.Delete(_tempDirectory, true); + } + } + + [Test] + public async Task Given_ValidDataAndFileName_When_SaveAsync_Then_FileIsSaved() + { + // Arrange + var data = _fixture.CreateMany(1000).ToArray(); + var fileName = _fixture.Create() + ".txt"; + var saveDate = DateTime.UtcNow; + + // Act + var url = await _service.SaveAsync(data, fileName); + + // Assert + Assert.That(url, Is.Not.Null); + + // _tempDirectory/uploads/yyyy/MM/url + var datePath = saveDate.ToString("yyyy/MM"); + var expectedFilePath = Path.Combine(_tempDirectory, "uploads", url); + Assert.That(File.Exists(expectedFilePath), Is.True, $"Файл не найден по пути: {expectedFilePath}"); + } + + [Test] + public void Given_EmptyName_When_SaveAsync_Should_Throw_ArgumentException() + { + // Arrange + var data = _fixture.CreateMany(1000).ToArray(); + + // Act & Assert + Assert.ThrowsAsync(async () => await _service.SaveAsync(data, string.Empty)); + } + + [Test] + public void Given_EmptyData_When_SaveAsync_Should_Throw_ArgumentException() + { + // Arrange + var name = _fixture.Create(); + + // Act & Assert + Assert.ThrowsAsync(async () => await _service.SaveAsync(default, name)); + } +} \ No newline at end of file diff --git a/Govor.API/Controllers/AuthController.cs b/Govor.API/Controllers/AuthController.cs index ca7928b..edc854e 100644 --- a/Govor.API/Controllers/AuthController.cs +++ b/Govor.API/Controllers/AuthController.cs @@ -1,6 +1,6 @@ using Govor.API.Services.Authentication; using Govor.Core.DTOs; -using Govor.Core.Services; +using Govor.API.Services.Authentication.Interfaces; using Microsoft.AspNetCore.Mvc; namespace Govor.API.Controllers; diff --git a/Govor.API/Controllers/InviteController.cs b/Govor.API/Controllers/InviteController.cs index ff4d808..dc2b00f 100644 --- a/Govor.API/Controllers/InviteController.cs +++ b/Govor.API/Controllers/InviteController.cs @@ -1,4 +1,4 @@ -using Govor.Core.Services; +using Govor.API.Services; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; diff --git a/Govor.API/Program.cs b/Govor.API/Program.cs index 7943ba5..5e6ff00 100644 --- a/Govor.API/Program.cs +++ b/Govor.API/Program.cs @@ -2,17 +2,15 @@ using System.Text; using Govor.API.Hubs; using Govor.API.Services; using Govor.API.Services.Authentication; +using Govor.API.Services.Authentication.Interfaces; using Govor.Core.Infrastructure.Extensions; using Govor.Core.Infrastructure.Validators; using Govor.Core.Models; -using Govor.Core.Repositories; using Govor.Core.Repositories.Users; -using Govor.Core.Services; using Govor.Data; using Govor.Data.Repositories; using Microsoft.EntityFrameworkCore; using Microsoft.AspNetCore.Authentication.JwtBearer; -using Microsoft.AspNetCore.Authorization; using Microsoft.IdentityModel.Tokens; var builder = WebApplication.CreateBuilder(args); diff --git a/Govor.API/Services/Authentication/AuthService.cs b/Govor.API/Services/Authentication/AuthService.cs index 79779fa..47f25ec 100644 --- a/Govor.API/Services/Authentication/AuthService.cs +++ b/Govor.API/Services/Authentication/AuthService.cs @@ -1,10 +1,10 @@ +using Govor.API.Services.Authentication.Interfaces; using Govor.Core; using Govor.Core.Infrastructure.Extensions; using Govor.Core.Models; -using Govor.Core.Repositories; using Govor.Core.Repositories.Users; -using Govor.Core.Services; -using Govor.Data.Repositories; +using Govor.API.Services; + namespace Govor.API.Services.Authentication; diff --git a/Govor.Core/Services/IAuthService.cs b/Govor.API/Services/Authentication/Interfaces/IAuthService.cs similarity index 77% rename from Govor.Core/Services/IAuthService.cs rename to Govor.API/Services/Authentication/Interfaces/IAuthService.cs index 98c3217..2cd87cb 100644 --- a/Govor.Core/Services/IAuthService.cs +++ b/Govor.API/Services/Authentication/Interfaces/IAuthService.cs @@ -1,6 +1,6 @@ using Govor.Core.Models; -namespace Govor.Core.Services; +namespace Govor.API.Services.Authentication.Interfaces; public interface IAccountService { diff --git a/Govor.Core/Services/IJwtService.cs b/Govor.API/Services/Authentication/Interfaces/IJwtService.cs similarity index 63% rename from Govor.Core/Services/IJwtService.cs rename to Govor.API/Services/Authentication/Interfaces/IJwtService.cs index 65bf53e..96a0c15 100644 --- a/Govor.Core/Services/IJwtService.cs +++ b/Govor.API/Services/Authentication/Interfaces/IJwtService.cs @@ -1,6 +1,6 @@ using Govor.Core.Models; -namespace Govor.Core.Services; +namespace Govor.API.Services.Authentication.Interfaces; public interface IJwtService { diff --git a/Govor.API/Services/Authentication/JwtService.cs b/Govor.API/Services/Authentication/JwtService.cs index 4cbff88..040eed9 100644 --- a/Govor.API/Services/Authentication/JwtService.cs +++ b/Govor.API/Services/Authentication/JwtService.cs @@ -1,8 +1,8 @@ using System.IdentityModel.Tokens.Jwt; using System.Security.Claims; using System.Text; +using Govor.API.Services.Authentication.Interfaces; using Govor.Core.Models; -using Govor.Core.Services; using Microsoft.Extensions.Options; using Microsoft.IdentityModel.Tokens; diff --git a/Govor.Core/Services/IGroupService.cs b/Govor.API/Services/IGroupService.cs similarity index 76% rename from Govor.Core/Services/IGroupService.cs rename to Govor.API/Services/IGroupService.cs index d3a5d42..955dc32 100644 --- a/Govor.Core/Services/IGroupService.cs +++ b/Govor.API/Services/IGroupService.cs @@ -1,6 +1,6 @@ using Govor.Core.Models; -namespace Govor.Core.Services; +namespace Govor.API.Services; public interface IGroupService { diff --git a/Govor.API/Services/IStorageService.cs b/Govor.API/Services/IStorageService.cs new file mode 100644 index 0000000..778eeeb --- /dev/null +++ b/Govor.API/Services/IStorageService.cs @@ -0,0 +1,10 @@ +using Microsoft.AspNetCore.Http; // для IFormFile + +namespace Govor.API.Services; + +public interface IStorageService +{ + Task SaveAsync(byte[] data, string fileName); + Task LoadAsync(string url); + Task RemoveAsync(string url); +} \ No newline at end of file diff --git a/Govor.API/Services/LocalStorageService.cs b/Govor.API/Services/LocalStorageService.cs new file mode 100644 index 0000000..61bac10 --- /dev/null +++ b/Govor.API/Services/LocalStorageService.cs @@ -0,0 +1,61 @@ +namespace Govor.API.Services; + +public class LocalStorageService : IStorageService +{ + private readonly string _storagePath; + + public LocalStorageService(IWebHostEnvironment hostingEnvironment) + { + _storagePath = Path.Combine(hostingEnvironment.ContentRootPath, "uploads"); + + if (!Directory.Exists(_storagePath)) + { + Directory.CreateDirectory(_storagePath); + } + } + + public async Task SaveAsync(byte[] data, string fileName) + { + if(data is null || data.Length == 0) + throw new ArgumentException("Invalid file", nameof(data)); + + if(fileName is null || fileName.Length == 0) + throw new ArgumentException("Invalid file name", nameof(fileName)); + + var date = DateTime.UtcNow.ToString("yyyy/MM"); + + var folder = Path.Combine(_storagePath, date); + Directory.CreateDirectory(folder); + + var uniqueFileName = $"{Guid.NewGuid()}_{Path.GetFileName(fileName)}"; + var fullPath = Path.Combine(folder, uniqueFileName); + + await using var stream = new FileStream(fullPath, FileMode.Create); + stream.WriteAsync(data, 0, data.Length); + + return Path.Combine(date, uniqueFileName); + } + + public async Task LoadAsync(string url) + { + var filePath = Path.Combine(_storagePath, Path.GetFileName(url)); + + if (!File.Exists(filePath)) + throw new FileNotFoundException("File not found.", filePath); + + await using var file = new FileStream(filePath, FileMode.Open, FileAccess.Read); + return file; + } + + public async Task RemoveAsync(string url) + { + var path = Path.Combine(_storagePath, Path.GetFileName(url)); + + if (File.Exists(path)) + { + File.Delete(path); + } + + await Task.CompletedTask; + } +} diff --git a/Govor.Core/Models/MediaAttachments.cs b/Govor.Core/Models/MediaAttachments.cs new file mode 100644 index 0000000..b26f857 --- /dev/null +++ b/Govor.Core/Models/MediaAttachments.cs @@ -0,0 +1,23 @@ +namespace Govor.Core.Models; + +public class MediaAttachments +{ + public Guid Id { get; set; } + public Guid MessageId { get; set; } + + public MediaType Type { get; set; } + public string FilePath { get; set; } = string.Empty; // путь к файлу (локальный или URL) + public string MimeType { get; set; } = string.Empty; + + public string? EncryptedKey { get; set; } // если используется отдельное шифрование + public Message Message { get; set; } = null!; +} + +public enum MediaType +{ + Image, + Video, + Audio, + File, + Voice +} \ No newline at end of file diff --git a/Govor.Core/Models/Message.cs b/Govor.Core/Models/Message.cs index 9e1a20c..3436fcd 100644 --- a/Govor.Core/Models/Message.cs +++ b/Govor.Core/Models/Message.cs @@ -10,6 +10,8 @@ public class Message public DateTime SentAt { get; set; } public bool IsEdited { get; set; } = false; public DateTime? EditedAt { get; set; } + public List Reactions { get; set; } = new List(); + public List MediaAttachments { get; set; } = new List(); } public enum RecipientType diff --git a/Govor.Core/Models/MessageReaction.cs b/Govor.Core/Models/MessageReaction.cs new file mode 100644 index 0000000..8e8fa80 --- /dev/null +++ b/Govor.Core/Models/MessageReaction.cs @@ -0,0 +1,15 @@ +namespace Govor.Core.Models; + +public class MessageReaction +{ + public Guid Id { get; set; } + + public Guid MessageId { get; set; } + public Message Message { get; set; } + + public Guid UserId { get; set; } + public User User { get; set; } + + public string ReactionCode { get; set; } // "❤️", "🔥", "👍", ":custom_emoji:" + public DateTime ReactedAt { get; set; } = DateTime.UtcNow; +} \ No newline at end of file