diff --git a/LICENSE b/LICENSE deleted file mode 100644 index 2222de4..0000000 --- a/LICENSE +++ /dev/null @@ -1,21 +0,0 @@ -MIT License - -Copyright (c) 2026 Knot Messenger - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. diff --git a/apps/server-net/build_log.txt b/apps/server-net/build_log.txt new file mode 100644 index 0000000..b8f969b Binary files /dev/null and b/apps/server-net/build_log.txt differ diff --git a/apps/server-net/build_log_err.txt b/apps/server-net/build_log_err.txt new file mode 100644 index 0000000..1a519ee Binary files /dev/null and b/apps/server-net/build_log_err.txt differ diff --git a/apps/server-net/out.txt b/apps/server-net/out.txt new file mode 100644 index 0000000..25bad3a --- /dev/null +++ b/apps/server-net/out.txt @@ -0,0 +1,27 @@ + Determining projects to restore... + All projects are up-to-date for restore. +C:\Program Files\dotnet\sdk\10.0.100-preview.6.25358.103\Sdks\Microsoft.NET.Sdk\targets\Microsoft.NET.RuntimeIdentifierInference.targets(335,5): message NETSDK1057: You are using a preview version of .NET. See: https://aka.ms/dotnet-support-policy [E:\GIT\forkmessager\apps\server-net\tests\Knot.Modules.Chats.UnitTests\Knot.Modules.Chats.UnitTests.csproj] +C:\Program Files\dotnet\sdk\10.0.100-preview.6.25358.103\Sdks\Microsoft.NET.Sdk\targets\Microsoft.NET.RuntimeIdentifierInference.targets(335,5): message NETSDK1057: You are using a preview version of .NET. See: https://aka.ms/dotnet-support-policy [E:\GIT\forkmessager\apps\server-net\tests\Knot.Modules.Identity.UnitTests\Knot.Modules.Identity.UnitTests.csproj] +C:\Program Files\dotnet\sdk\10.0.100-preview.6.25358.103\Sdks\Microsoft.NET.Sdk\targets\Microsoft.NET.RuntimeIdentifierInference.targets(335,5): message NETSDK1057: You are using a preview version of .NET. See: https://aka.ms/dotnet-support-policy [E:\GIT\forkmessager\apps\server-net\src\Modules\Identity\Knot.Modules.Identity.csproj] +C:\Program Files\dotnet\sdk\10.0.100-preview.6.25358.103\Sdks\Microsoft.NET.Sdk\targets\Microsoft.NET.RuntimeIdentifierInference.targets(335,5): message NETSDK1057: You are using a preview version of .NET. See: https://aka.ms/dotnet-support-policy [E:\GIT\forkmessager\apps\server-net\src\Shared\Knot.Shared.Infrastructure\Knot.Shared.Infrastructure.csproj] +C:\Program Files\dotnet\sdk\10.0.100-preview.6.25358.103\Sdks\Microsoft.NET.Sdk\targets\Microsoft.NET.RuntimeIdentifierInference.targets(335,5): message NETSDK1057: You are using a preview version of .NET. See: https://aka.ms/dotnet-support-policy [E:\GIT\forkmessager\apps\server-net\src\Modules\Chats\Knot.Modules.Chats.csproj] +C:\Program Files\dotnet\sdk\10.0.100-preview.6.25358.103\Sdks\Microsoft.NET.Sdk\targets\Microsoft.NET.RuntimeIdentifierInference.targets(335,5): message NETSDK1057: You are using a preview version of .NET. See: https://aka.ms/dotnet-support-policy [E:\GIT\forkmessager\apps\server-net\src\Shared\Knot.Shared.Kernel\Knot.Shared.Kernel.csproj] + Knot.Shared.Kernel -> E:\GIT\forkmessager\apps\server-net\src\Shared\Knot.Shared.Kernel\bin\Debug\net10.0\Knot.Shared.Kernel.dll +E:\GIT\forkmessager\apps\server-net\src\Modules\Identity\Domain\Story.cs(23,15): warning CS8618: Non-nullable property 'Type' must contain a non-null value when exiting constructor. Consider adding the 'required' modifier or declaring the property as nullable. [E:\GIT\forkmessager\apps\server-net\src\Modules\Identity\Knot.Modules.Identity.csproj] +E:\GIT\forkmessager\apps\server-net\src\Shared\Knot.Shared.Infrastructure\Storage\S3FileStorageService.cs(116,27): warning CS8600: Converting null literal or possible null value to non-nullable type. [E:\GIT\forkmessager\apps\server-net\src\Shared\Knot.Shared.Infrastructure\Knot.Shared.Infrastructure.csproj] +E:\GIT\forkmessager\apps\server-net\src\Shared\Knot.Shared.Infrastructure\Storage\S3FileStorageService.cs(142,47): warning CS8604: Possible null reference argument for parameter 's' in 'byte[] Convert.FromBase64String(string s)'. [E:\GIT\forkmessager\apps\server-net\src\Shared\Knot.Shared.Infrastructure\Knot.Shared.Infrastructure.csproj] + Knot.Modules.Identity -> E:\GIT\forkmessager\apps\server-net\src\Modules\Identity\bin\Debug\net10.0\Knot.Modules.Identity.dll + Knot.Shared.Infrastructure -> E:\GIT\forkmessager\apps\server-net\src\Shared\Knot.Shared.Infrastructure\bin\Debug\net10.0\Knot.Shared.Infrastructure.dll + Knot.Modules.Chats -> E:\GIT\forkmessager\apps\server-net\src\Modules\Chats\bin\Debug\net10.0\Knot.Modules.Chats.dll + Knot.Modules.Identity.UnitTests -> E:\GIT\forkmessager\apps\server-net\tests\Knot.Modules.Identity.UnitTests\bin\Debug\net10.0\Knot.Modules.Identity.UnitTests.dll + Knot.Modules.Chats.UnitTests -> E:\GIT\forkmessager\apps\server-net\tests\Knot.Modules.Chats.UnitTests\bin\Debug\net10.0\Knot.Modules.Chats.UnitTests.dll + +Build succeeded. + +E:\GIT\forkmessager\apps\server-net\src\Modules\Identity\Domain\Story.cs(23,15): warning CS8618: Non-nullable property 'Type' must contain a non-null value when exiting constructor. Consider adding the 'required' modifier or declaring the property as nullable. [E:\GIT\forkmessager\apps\server-net\src\Modules\Identity\Knot.Modules.Identity.csproj] +E:\GIT\forkmessager\apps\server-net\src\Shared\Knot.Shared.Infrastructure\Storage\S3FileStorageService.cs(116,27): warning CS8600: Converting null literal or possible null value to non-nullable type. [E:\GIT\forkmessager\apps\server-net\src\Shared\Knot.Shared.Infrastructure\Knot.Shared.Infrastructure.csproj] +E:\GIT\forkmessager\apps\server-net\src\Shared\Knot.Shared.Infrastructure\Storage\S3FileStorageService.cs(142,47): warning CS8604: Possible null reference argument for parameter 's' in 'byte[] Convert.FromBase64String(string s)'. [E:\GIT\forkmessager\apps\server-net\src\Shared\Knot.Shared.Infrastructure\Knot.Shared.Infrastructure.csproj] + 3 Warning(s) + 0 Error(s) + +Time Elapsed 00:00:01.32 diff --git a/apps/server-net/out8.txt b/apps/server-net/out8.txt new file mode 100644 index 0000000..216d0bd --- /dev/null +++ b/apps/server-net/out8.txt @@ -0,0 +1,23 @@ + Determining projects to restore... + All projects are up-to-date for restore. +C:\Program Files\dotnet\sdk\10.0.100-preview.6.25358.103\Sdks\Microsoft.NET.Sdk\targets\Microsoft.NET.RuntimeIdentifierInference.targets(335,5): message NETSDK1057: You are using a preview version of .NET. See: https://aka.ms/dotnet-support-policy [E:\GIT\forkmessager\apps\server-net\src\Modules\Chats\Knot.Modules.Chats.csproj] +C:\Program Files\dotnet\sdk\10.0.100-preview.6.25358.103\Sdks\Microsoft.NET.Sdk\targets\Microsoft.NET.RuntimeIdentifierInference.targets(335,5): message NETSDK1057: You are using a preview version of .NET. See: https://aka.ms/dotnet-support-policy [E:\GIT\forkmessager\apps\server-net\src\Shared\Knot.Shared.Infrastructure\Knot.Shared.Infrastructure.csproj] +C:\Program Files\dotnet\sdk\10.0.100-preview.6.25358.103\Sdks\Microsoft.NET.Sdk\targets\Microsoft.NET.RuntimeIdentifierInference.targets(335,5): message NETSDK1057: You are using a preview version of .NET. See: https://aka.ms/dotnet-support-policy [E:\GIT\forkmessager\apps\server-net\tests\Knot.Modules.Chats.UnitTests\Knot.Modules.Chats.UnitTests.csproj] +C:\Program Files\dotnet\sdk\10.0.100-preview.6.25358.103\Sdks\Microsoft.NET.Sdk\targets\Microsoft.NET.RuntimeIdentifierInference.targets(335,5): message NETSDK1057: You are using a preview version of .NET. See: https://aka.ms/dotnet-support-policy [E:\GIT\forkmessager\apps\server-net\tests\Knot.Modules.Identity.UnitTests\Knot.Modules.Identity.UnitTests.csproj] +C:\Program Files\dotnet\sdk\10.0.100-preview.6.25358.103\Sdks\Microsoft.NET.Sdk\targets\Microsoft.NET.RuntimeIdentifierInference.targets(335,5): message NETSDK1057: You are using a preview version of .NET. See: https://aka.ms/dotnet-support-policy [E:\GIT\forkmessager\apps\server-net\src\Modules\Identity\Knot.Modules.Identity.csproj] +C:\Program Files\dotnet\sdk\10.0.100-preview.6.25358.103\Sdks\Microsoft.NET.Sdk\targets\Microsoft.NET.RuntimeIdentifierInference.targets(335,5): message NETSDK1057: You are using a preview version of .NET. See: https://aka.ms/dotnet-support-policy [E:\GIT\forkmessager\apps\server-net\src\Shared\Knot.Shared.Kernel\Knot.Shared.Kernel.csproj] + Knot.Shared.Kernel -> E:\GIT\forkmessager\apps\server-net\src\Shared\Knot.Shared.Kernel\bin\Debug\net10.0\Knot.Shared.Kernel.dll + Knot.Modules.Identity -> E:\GIT\forkmessager\apps\server-net\src\Modules\Identity\bin\Debug\net10.0\Knot.Modules.Identity.dll + Knot.Shared.Infrastructure -> E:\GIT\forkmessager\apps\server-net\src\Shared\Knot.Shared.Infrastructure\bin\Debug\net10.0\Knot.Shared.Infrastructure.dll + Knot.Modules.Identity.UnitTests -> E:\GIT\forkmessager\apps\server-net\tests\Knot.Modules.Identity.UnitTests\bin\Debug\net10.0\Knot.Modules.Identity.UnitTests.dll +E:\GIT\forkmessager\apps\server-net\src\Modules\Chats\Infrastructure\Persistence\ChatsDbContext.cs(16,34): error CS0234: The type or namespace name 'Infrastructure' does not exist in the namespace 'Knot.Shared' (are you missing an assembly reference?) [E:\GIT\forkmessager\apps\server-net\src\Modules\Chats\Knot.Modules.Chats.csproj] +E:\GIT\forkmessager\apps\server-net\src\Modules\Chats\Infrastructure\Persistence\ChatsDbContext.cs(18,101): error CS0234: The type or namespace name 'Infrastructure' does not exist in the namespace 'Knot.Shared' (are you missing an assembly reference?) [E:\GIT\forkmessager\apps\server-net\src\Modules\Chats\Knot.Modules.Chats.csproj] + +Build FAILED. + +E:\GIT\forkmessager\apps\server-net\src\Modules\Chats\Infrastructure\Persistence\ChatsDbContext.cs(16,34): error CS0234: The type or namespace name 'Infrastructure' does not exist in the namespace 'Knot.Shared' (are you missing an assembly reference?) [E:\GIT\forkmessager\apps\server-net\src\Modules\Chats\Knot.Modules.Chats.csproj] +E:\GIT\forkmessager\apps\server-net\src\Modules\Chats\Infrastructure\Persistence\ChatsDbContext.cs(18,101): error CS0234: The type or namespace name 'Infrastructure' does not exist in the namespace 'Knot.Shared' (are you missing an assembly reference?) [E:\GIT\forkmessager\apps\server-net\src\Modules\Chats\Knot.Modules.Chats.csproj] + 0 Warning(s) + 2 Error(s) + +Time Elapsed 00:00:00.89 diff --git a/apps/server-net/src/Host/Controllers/ChatsController.cs b/apps/server-net/src/Host/Controllers/ChatsController.cs index ce8fb15..03c3fad 100644 --- a/apps/server-net/src/Host/Controllers/ChatsController.cs +++ b/apps/server-net/src/Host/Controllers/ChatsController.cs @@ -120,6 +120,15 @@ public sealed class ChatsController : ControllerBase }); } + if (c.Type == ChatType.Personal && messagesList.Count == 0) + { + var currentMember = c.Members.FirstOrDefault(m => m.UserId == _userContext.UserId); + if (currentMember == null || currentMember.Role != ChatRole.Owner) + { + continue; // Skip returning this empty personal chat to the non-initiator + } + } + result.Add(new { id = c.Id, @@ -209,7 +218,8 @@ public sealed class ChatsController : ControllerBase public async Task LeaveOrDeleteChat(Guid id, [FromServices] IChatRepository chatRepository, [FromServices] IChatsUnitOfWork uow, CancellationToken ct) { var chat = await chatRepository.GetByIdAsync(id, ct); - if (chat == null || !chat.Members.Any(m => m.UserId == _userContext.UserId)) return NotFound(); + if (chat == null) return Ok(new { success = true }); // Already physically deleted, idempotent + if (!chat.Members.Any(m => m.UserId == _userContext.UserId)) return Forbid(); if (chat.Type == ChatType.Group) { @@ -280,21 +290,16 @@ public sealed class ChatsController : ControllerBase } [HttpPost("{id:guid}/avatar")] - public async Task UploadGroupAvatar(Guid id, IFormFile avatar, [FromServices] IChatRepository chatRepository, [FromServices] IChatsUnitOfWork uow, CancellationToken ct) + public async Task UploadGroupAvatar(Guid id, IFormFile avatar, [FromServices] IChatRepository chatRepository, [FromServices] IChatsUnitOfWork uow, [FromServices] Knot.Shared.Kernel.Storage.IFileStorageService fileStorage, CancellationToken ct) { var chat = await chatRepository.GetByIdAsync(id, ct); if (chat == null || !chat.Members.Any(m => m.UserId == _userContext.UserId)) return NotFound(); if (avatar == null || avatar.Length == 0) return BadRequest("No file"); - var uploadsPath = Path.Combine(Directory.GetCurrentDirectory(), "uploads", "avatars"); - if (!Directory.Exists(uploadsPath)) Directory.CreateDirectory(uploadsPath); + using var stream = avatar.OpenReadStream(); + var fileId = await fileStorage.UploadFileAsync(stream, avatar.FileName, avatar.ContentType); + var url = $"/api/files/{fileId}"; - var fileName = $"{Guid.NewGuid()}{Path.GetExtension(avatar.FileName)}"; - var filePath = Path.Combine(uploadsPath, fileName); - using (var stream = new FileStream(filePath, FileMode.Create)) - await avatar.CopyToAsync(stream, ct); - - var url = $"/uploads/avatars/{fileName}"; chat.UpdateAvatar(url); chatRepository.Update(chat); await uow.SaveChangesAsync(ct); @@ -303,17 +308,13 @@ public sealed class ChatsController : ControllerBase } [HttpPost("{id:guid}/avatar/crop")] - public async Task CropGroupAvatar(Guid id, [FromForm] IFormFile avatar, [FromForm] int x, [FromForm] int y, [FromForm] int width, [FromForm] int height, [FromServices] IChatRepository chatRepository, [FromServices] IChatsUnitOfWork uow, CancellationToken ct) + public async Task CropGroupAvatar(Guid id, [FromForm] IFormFile avatar, [FromForm] int x, [FromForm] int y, [FromForm] int width, [FromForm] int height, [FromServices] IChatRepository chatRepository, [FromServices] IChatsUnitOfWork uow, [FromServices] Knot.Shared.Kernel.Storage.IFileStorageService fileStorage, CancellationToken ct) { var chat = await chatRepository.GetByIdAsync(id, ct); if (chat == null || !chat.Members.Any(m => m.UserId == _userContext.UserId)) return NotFound(); if (avatar == null || avatar.Length == 0) return BadRequest("No file"); - var uploadsPath = Path.Combine(Directory.GetCurrentDirectory(), "uploads", "avatars"); - if (!Directory.Exists(uploadsPath)) Directory.CreateDirectory(uploadsPath); - - var fileName = $"{Guid.NewGuid()}.jpg"; - var filePath = Path.Combine(uploadsPath, fileName); + string url; try { @@ -327,7 +328,14 @@ public sealed class ChatsController : ControllerBase image.Mutate(ctx => ctx.Crop(new SixLabors.ImageSharp.Rectangle(startX, startY, rectWidth, rectHeight))); image.Mutate(ctx => ctx.Resize(400, 400)); - await image.SaveAsJpegAsync(filePath, ct); + + using var outStream = new MemoryStream(); + await image.SaveAsJpegAsync(outStream, ct); + outStream.Position = 0; + + var fileName = avatar.FileName ?? "avatar.jpg"; + var fileId = await fileStorage.UploadFileAsync(outStream, fileName, "image/jpeg"); + url = $"/api/files/{fileId}"; } } catch (Exception ex) @@ -335,7 +343,6 @@ public sealed class ChatsController : ControllerBase return StatusCode(500, "Error processing image: " + ex.Message); } - var url = $"/uploads/avatars/{fileName}"; chat.UpdateAvatar(url); chatRepository.Update(chat); await uow.SaveChangesAsync(ct); diff --git a/apps/server-net/src/Host/Controllers/FilesController.cs b/apps/server-net/src/Host/Controllers/FilesController.cs new file mode 100644 index 0000000..46960ea --- /dev/null +++ b/apps/server-net/src/Host/Controllers/FilesController.cs @@ -0,0 +1,33 @@ +using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Authorization; +using Knot.Shared.Kernel.Storage; + +namespace Host.Controllers; + +[ApiController] +[Route("api/files")] +public sealed class FilesController : ControllerBase +{ + private readonly IFileStorageService _fileStorage; + + public FilesController(IFileStorageService fileStorage) + { + _fileStorage = fileStorage; + } + + [HttpGet("{id}")] + [AllowAnonymous] + public async Task DownloadFile(string id) + { + try + { + var result = await _fileStorage.DownloadFileAsync(id); + // Обратите внимание, что мы возвращаем поток с автоматическим освобождением памяти. + return File(result.Stream, result.ContentType, result.FileName); + } + catch (Exception ex) + { + return NotFound(new { error = "Файл не найден или ошибка доступа.", message = ex.Message }); + } + } +} diff --git a/apps/server-net/src/Host/Controllers/MessagesController.cs b/apps/server-net/src/Host/Controllers/MessagesController.cs index 829ec40..3a5a02e 100644 --- a/apps/server-net/src/Host/Controllers/MessagesController.cs +++ b/apps/server-net/src/Host/Controllers/MessagesController.cs @@ -187,22 +187,14 @@ public sealed class MessagesController : ControllerBase } [HttpPost("upload")] - public async Task UploadFile(IFormFile file) + public async Task UploadFile(IFormFile file, [FromServices] Knot.Shared.Kernel.Storage.IFileStorageService fileStorage) { if (file == null || file.Length == 0) return BadRequest("No file uploaded"); - var uploadsPath = Path.Combine(Directory.GetCurrentDirectory(), "uploads"); - if (!Directory.Exists(uploadsPath)) Directory.CreateDirectory(uploadsPath); - - var fileName = $"{Guid.NewGuid()}{Path.GetExtension(file.FileName)}"; - var filePath = Path.Combine(uploadsPath, fileName); - - using (var stream = new FileStream(filePath, FileMode.Create)) - { - await file.CopyToAsync(stream); - } + using var stream = file.OpenReadStream(); + var fileId = await fileStorage.UploadFileAsync(stream, file.FileName, file.ContentType); - return Ok(new { url = $"/uploads/{fileName}", filename = file.FileName, size = file.Length }); + return Ok(new { url = $"/api/files/{fileId}", filename = file.FileName, size = file.Length }); } [HttpGet("chat/{chatId:guid}/shared")] diff --git a/apps/server-net/src/Host/Controllers/UsersController.cs b/apps/server-net/src/Host/Controllers/UsersController.cs index 69b7cf7..c1589e5 100644 --- a/apps/server-net/src/Host/Controllers/UsersController.cs +++ b/apps/server-net/src/Host/Controllers/UsersController.cs @@ -64,45 +64,19 @@ public sealed class UsersController : ControllerBase } [HttpPost("avatar")] - public async Task UploadAvatar(IFormFile avatar, CancellationToken ct) + public async Task UploadAvatar(IFormFile avatar, [FromServices] Knot.Shared.Kernel.Storage.IFileStorageService fileStorage, CancellationToken ct) { var user = await _userRepository.GetByIdAsync(_userContext.UserId, ct); if (user == null) return NotFound(); string? avatarUrl = null; + var fileToUpload = avatar != null && avatar.Length > 0 ? avatar : Request.Form.Files.Count > 0 ? Request.Form.Files[0] : null; - if (avatar != null && avatar.Length > 0) + if (fileToUpload != null) { - var uploadsPath = Path.Combine(Directory.GetCurrentDirectory(), "uploads", "avatars"); - if (!Directory.Exists(uploadsPath)) Directory.CreateDirectory(uploadsPath); - - var ext = Path.GetExtension(avatar.FileName); - var fileName = $"{Guid.NewGuid()}{ext}"; - var filePath = Path.Combine(uploadsPath, fileName); - - using (var stream = new FileStream(filePath, FileMode.Create)) - { - await avatar.CopyToAsync(stream, ct); - } - - avatarUrl = $"/uploads/avatars/{fileName}"; - } - else if (Request.Form.Files.Count > 0) - { - var file = Request.Form.Files[0]; - var uploadsPath = Path.Combine(Directory.GetCurrentDirectory(), "uploads", "avatars"); - if (!Directory.Exists(uploadsPath)) Directory.CreateDirectory(uploadsPath); - - var ext = Path.GetExtension(file.FileName); - var fileName = $"{Guid.NewGuid()}{ext}"; - var filePath = Path.Combine(uploadsPath, fileName); - - using (var stream = new FileStream(filePath, FileMode.Create)) - { - await file.CopyToAsync(stream, ct); - } - - avatarUrl = $"/uploads/avatars/{fileName}"; + using var stream = fileToUpload.OpenReadStream(); + var fileId = await fileStorage.UploadFileAsync(stream, fileToUpload.FileName, fileToUpload.ContentType); + avatarUrl = $"/api/files/{fileId}"; } else { @@ -124,26 +98,20 @@ public sealed class UsersController : ControllerBase } [HttpPost("avatar/crop")] - public async Task CropAvatar([FromForm] IFormFile avatar, [FromForm] int x, [FromForm] int y, [FromForm] int width, [FromForm] int height, CancellationToken ct) + public async Task CropAvatar([FromForm] IFormFile avatar, [FromForm] int x, [FromForm] int y, [FromForm] int width, [FromForm] int height, [FromServices] Knot.Shared.Kernel.Storage.IFileStorageService fileStorage, CancellationToken ct) { var user = await _userRepository.GetByIdAsync(_userContext.UserId, ct); if (user == null) return NotFound(); if (avatar == null || avatar.Length == 0) return BadRequest("No file"); - var uploadsPath = Path.Combine(Directory.GetCurrentDirectory(), "uploads", "avatars"); - if (!Directory.Exists(uploadsPath)) Directory.CreateDirectory(uploadsPath); - - var ext = ".jpg"; - var fileName = $"{Guid.NewGuid()}{ext}"; - var filePath = Path.Combine(uploadsPath, fileName); + string avatarUrl; try { using (var inputStream = avatar.OpenReadStream()) using (var image = await SixLabors.ImageSharp.Image.LoadAsync(inputStream)) { - // Clamp coordinates to image bounds int startX = Math.Max(0, Math.Min(x, image.Width - 1)); int startY = Math.Max(0, Math.Min(y, image.Height - 1)); int rectWidth = Math.Max(1, Math.Min(width, image.Width - startX)); @@ -151,8 +119,14 @@ public sealed class UsersController : ControllerBase image.Mutate(ctx => ctx.Crop(new SixLabors.ImageSharp.Rectangle(startX, startY, rectWidth, rectHeight))); image.Mutate(ctx => ctx.Resize(400, 400)); + + using var outStream = new MemoryStream(); + await image.SaveAsJpegAsync(outStream, ct); + outStream.Position = 0; - await image.SaveAsJpegAsync(filePath, ct); + var fileName = avatar.FileName ?? "avatar.jpg"; + var id = await fileStorage.UploadFileAsync(outStream, fileName, "image/jpeg"); + avatarUrl = $"/api/files/{id}"; } } catch (Exception ex) @@ -160,7 +134,6 @@ public sealed class UsersController : ControllerBase return StatusCode(500, "Error processing image: " + ex.Message); } - var avatarUrl = $"/uploads/avatars/{fileName}"; user.UpdateAvatar(avatarUrl); await _unitOfWork.SaveChangesAsync(ct); diff --git a/apps/server-net/src/Host/Program.cs b/apps/server-net/src/Host/Program.cs index f7e2b2f..78db633 100644 --- a/apps/server-net/src/Host/Program.cs +++ b/apps/server-net/src/Host/Program.cs @@ -31,7 +31,12 @@ var envMappings = new Dictionary ["Jwt:Audience"] = builder.Configuration["JWT_AUDIENCE"], ["WebRtc:TurnUrl"] = builder.Configuration["TURN_URL"], ["WebRtc:TurnUsername"] = builder.Configuration["TURN_USERNAME"], - ["WebRtc:TurnPassword"] = builder.Configuration["TURN_PASSWORD"] + ["WebRtc:TurnPassword"] = builder.Configuration["TURN_PASSWORD"], + ["KNOT_MASTER_ENCRYPTION_KEY"] = builder.Configuration["KNOT_MASTER_ENCRYPTION_KEY"], + ["S3_ENDPOINT"] = builder.Configuration["S3_ENDPOINT"], + ["S3_ACCESS_KEY"] = builder.Configuration["S3_ACCESS_KEY"], + ["S3_SECRET_KEY"] = builder.Configuration["S3_SECRET_KEY"], + ["S3_BUCKET"] = builder.Configuration["S3_BUCKET"] }; // Добавляем только те, что реально заданы в ENV @@ -41,7 +46,7 @@ builder.Configuration.AddInMemoryCollection( builder.Services.AddIdentityModule(builder.Configuration); builder.Services.AddChatsModule(builder.Configuration); -builder.Services.AddSharedInfrastructure(); +builder.Services.AddSharedInfrastructure(builder.Configuration); // Настройка CORS builder.Services.AddCors(options => @@ -147,17 +152,7 @@ if (app.Environment.IsDevelopment()) // app.UseHttpsRedirection(); -var uploadsPath = Path.Combine(Directory.GetCurrentDirectory(), "uploads"); -if (!Directory.Exists(uploadsPath)) -{ - Directory.CreateDirectory(uploadsPath); -} - -app.UseStaticFiles(new StaticFileOptions -{ - FileProvider = new PhysicalFileProvider(uploadsPath), - RequestPath = "/uploads" -}); +// Не раздаем статические файлы, так как теперь используем MinIO app.UseAuthentication(); app.UseAuthorization(); diff --git a/apps/server-net/src/Modules/Chats/Application/Chats/Create/CreateChatCommandHandler.cs b/apps/server-net/src/Modules/Chats/Application/Chats/Create/CreateChatCommandHandler.cs index 6e0430e..e4526f8 100644 --- a/apps/server-net/src/Modules/Chats/Application/Chats/Create/CreateChatCommandHandler.cs +++ b/apps/server-net/src/Modules/Chats/Application/Chats/Create/CreateChatCommandHandler.cs @@ -31,7 +31,7 @@ public sealed class CreateChatCommandHandler : ICommandHandler options, IMediator mediator) - + public ChatsDbContext(DbContextOptions options, IMediator mediator, Knot.Shared.Kernel.Security.IEncryptionService encryptionService) : base(options) - { _mediator = mediator; + _encryptionService = encryptionService; } public DbSet Chats => Set(); @@ -59,6 +59,12 @@ public sealed class ChatsDbContext : DbContext, IChatsUnitOfWork builder.ToTable("Messages"); builder.HasKey(m => m.Id); + builder.Property(m => m.Content) + .HasConversion( + v => v == null ? null : _encryptionService.EncryptMessage(v), + v => v == null ? null : _encryptionService.DecryptMessage(v) + ); + builder.OwnsMany(m => m.Media, mb => { diff --git a/apps/server-net/src/Modules/Chats/Infrastructure/Persistence/Migrations/20260311180825_InitialChats.Designer.cs b/apps/server-net/src/Modules/Chats/Infrastructure/Persistence/Migrations/20260311180825_InitialChats.Designer.cs index f84f4a4..23743a0 100644 --- a/apps/server-net/src/Modules/Chats/Infrastructure/Persistence/Migrations/20260311180825_InitialChats.Designer.cs +++ b/apps/server-net/src/Modules/Chats/Infrastructure/Persistence/Migrations/20260311180825_InitialChats.Designer.cs @@ -1,4 +1,4 @@ -// +// using System; using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Infrastructure; diff --git a/apps/server-net/src/Modules/Chats/Infrastructure/Persistence/Migrations/20260311180825_InitialChats.cs b/apps/server-net/src/Modules/Chats/Infrastructure/Persistence/Migrations/20260311180825_InitialChats.cs index 5537f8c..a4f70f4 100644 --- a/apps/server-net/src/Modules/Chats/Infrastructure/Persistence/Migrations/20260311180825_InitialChats.cs +++ b/apps/server-net/src/Modules/Chats/Infrastructure/Persistence/Migrations/20260311180825_InitialChats.cs @@ -1,4 +1,4 @@ -using System; +using System; using Microsoft.EntityFrameworkCore.Migrations; #nullable disable diff --git a/apps/server-net/src/Modules/Chats/Infrastructure/Persistence/Migrations/20260311215929_UpdateChatModel.Designer.cs b/apps/server-net/src/Modules/Chats/Infrastructure/Persistence/Migrations/20260311215929_UpdateChatModel.Designer.cs index 9afec5d..1a65891 100644 --- a/apps/server-net/src/Modules/Chats/Infrastructure/Persistence/Migrations/20260311215929_UpdateChatModel.Designer.cs +++ b/apps/server-net/src/Modules/Chats/Infrastructure/Persistence/Migrations/20260311215929_UpdateChatModel.Designer.cs @@ -1,4 +1,4 @@ -// +// using System; using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Infrastructure; diff --git a/apps/server-net/src/Modules/Chats/Infrastructure/Persistence/Migrations/20260311215929_UpdateChatModel.cs b/apps/server-net/src/Modules/Chats/Infrastructure/Persistence/Migrations/20260311215929_UpdateChatModel.cs index 8bbf309..0897202 100644 --- a/apps/server-net/src/Modules/Chats/Infrastructure/Persistence/Migrations/20260311215929_UpdateChatModel.cs +++ b/apps/server-net/src/Modules/Chats/Infrastructure/Persistence/Migrations/20260311215929_UpdateChatModel.cs @@ -1,4 +1,4 @@ -using System; +using System; using Microsoft.EntityFrameworkCore.Migrations; #nullable disable diff --git a/apps/server-net/src/Modules/Chats/Infrastructure/Persistence/Migrations/20260312094130_AddForwardedFromToMessages.Designer.cs b/apps/server-net/src/Modules/Chats/Infrastructure/Persistence/Migrations/20260312094130_AddForwardedFromToMessages.Designer.cs index 7c6af95..fc681e9 100644 --- a/apps/server-net/src/Modules/Chats/Infrastructure/Persistence/Migrations/20260312094130_AddForwardedFromToMessages.Designer.cs +++ b/apps/server-net/src/Modules/Chats/Infrastructure/Persistence/Migrations/20260312094130_AddForwardedFromToMessages.Designer.cs @@ -1,4 +1,4 @@ -// +// using System; using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Infrastructure; diff --git a/apps/server-net/src/Modules/Chats/Infrastructure/Persistence/Migrations/20260312094130_AddForwardedFromToMessages.cs b/apps/server-net/src/Modules/Chats/Infrastructure/Persistence/Migrations/20260312094130_AddForwardedFromToMessages.cs index 90616d5..24c7894 100644 --- a/apps/server-net/src/Modules/Chats/Infrastructure/Persistence/Migrations/20260312094130_AddForwardedFromToMessages.cs +++ b/apps/server-net/src/Modules/Chats/Infrastructure/Persistence/Migrations/20260312094130_AddForwardedFromToMessages.cs @@ -1,4 +1,4 @@ -using System; +using System; using Microsoft.EntityFrameworkCore.Migrations; #nullable disable diff --git a/apps/server-net/src/Modules/Chats/Infrastructure/Persistence/Migrations/20260312100324_AddReactionsToMessages.Designer.cs b/apps/server-net/src/Modules/Chats/Infrastructure/Persistence/Migrations/20260312100324_AddReactionsToMessages.Designer.cs index d08d3ff..880b952 100644 --- a/apps/server-net/src/Modules/Chats/Infrastructure/Persistence/Migrations/20260312100324_AddReactionsToMessages.Designer.cs +++ b/apps/server-net/src/Modules/Chats/Infrastructure/Persistence/Migrations/20260312100324_AddReactionsToMessages.Designer.cs @@ -1,4 +1,4 @@ -// +// using System; using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Infrastructure; diff --git a/apps/server-net/src/Modules/Chats/Infrastructure/Persistence/Migrations/20260312100324_AddReactionsToMessages.cs b/apps/server-net/src/Modules/Chats/Infrastructure/Persistence/Migrations/20260312100324_AddReactionsToMessages.cs index 50e7319..409f229 100644 --- a/apps/server-net/src/Modules/Chats/Infrastructure/Persistence/Migrations/20260312100324_AddReactionsToMessages.cs +++ b/apps/server-net/src/Modules/Chats/Infrastructure/Persistence/Migrations/20260312100324_AddReactionsToMessages.cs @@ -1,4 +1,4 @@ -using System; +using System; using Microsoft.EntityFrameworkCore.Migrations; #nullable disable diff --git a/apps/server-net/src/Modules/Chats/Infrastructure/Persistence/Migrations/20260312174910_UpdateChatsSchema.Designer.cs b/apps/server-net/src/Modules/Chats/Infrastructure/Persistence/Migrations/20260312174910_UpdateChatsSchema.Designer.cs index d02310a..67f4e52 100644 --- a/apps/server-net/src/Modules/Chats/Infrastructure/Persistence/Migrations/20260312174910_UpdateChatsSchema.Designer.cs +++ b/apps/server-net/src/Modules/Chats/Infrastructure/Persistence/Migrations/20260312174910_UpdateChatsSchema.Designer.cs @@ -1,4 +1,4 @@ -// +// using System; using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Infrastructure; diff --git a/apps/server-net/src/Modules/Chats/Infrastructure/Persistence/Migrations/20260312174910_UpdateChatsSchema.cs b/apps/server-net/src/Modules/Chats/Infrastructure/Persistence/Migrations/20260312174910_UpdateChatsSchema.cs index faeb196..59c4da4 100644 --- a/apps/server-net/src/Modules/Chats/Infrastructure/Persistence/Migrations/20260312174910_UpdateChatsSchema.cs +++ b/apps/server-net/src/Modules/Chats/Infrastructure/Persistence/Migrations/20260312174910_UpdateChatsSchema.cs @@ -1,4 +1,4 @@ -using System; +using System; using Microsoft.EntityFrameworkCore.Migrations; #nullable disable diff --git a/apps/server-net/src/Modules/Chats/Infrastructure/Persistence/Migrations/20260312204842_SupportPinningAndMuting.Designer.cs b/apps/server-net/src/Modules/Chats/Infrastructure/Persistence/Migrations/20260312204842_SupportPinningAndMuting.Designer.cs index b7486bf..92d4049 100644 --- a/apps/server-net/src/Modules/Chats/Infrastructure/Persistence/Migrations/20260312204842_SupportPinningAndMuting.Designer.cs +++ b/apps/server-net/src/Modules/Chats/Infrastructure/Persistence/Migrations/20260312204842_SupportPinningAndMuting.Designer.cs @@ -1,4 +1,4 @@ -// +// using System; using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Infrastructure; diff --git a/apps/server-net/src/Modules/Chats/Infrastructure/Persistence/Migrations/20260312204842_SupportPinningAndMuting.cs b/apps/server-net/src/Modules/Chats/Infrastructure/Persistence/Migrations/20260312204842_SupportPinningAndMuting.cs index 3d89355..df87a35 100644 --- a/apps/server-net/src/Modules/Chats/Infrastructure/Persistence/Migrations/20260312204842_SupportPinningAndMuting.cs +++ b/apps/server-net/src/Modules/Chats/Infrastructure/Persistence/Migrations/20260312204842_SupportPinningAndMuting.cs @@ -1,4 +1,4 @@ -using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Migrations; #nullable disable diff --git a/apps/server-net/src/Modules/Chats/Infrastructure/Persistence/Migrations/20260313183634_AddStoryIdToMessages.Designer.cs b/apps/server-net/src/Modules/Chats/Infrastructure/Persistence/Migrations/20260313183634_AddStoryIdToMessages.Designer.cs index e141b14..f521fb8 100644 --- a/apps/server-net/src/Modules/Chats/Infrastructure/Persistence/Migrations/20260313183634_AddStoryIdToMessages.Designer.cs +++ b/apps/server-net/src/Modules/Chats/Infrastructure/Persistence/Migrations/20260313183634_AddStoryIdToMessages.Designer.cs @@ -1,4 +1,4 @@ -// +// using System; using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Infrastructure; diff --git a/apps/server-net/src/Modules/Chats/Infrastructure/Persistence/Migrations/20260313183634_AddStoryIdToMessages.cs b/apps/server-net/src/Modules/Chats/Infrastructure/Persistence/Migrations/20260313183634_AddStoryIdToMessages.cs index 91f7a32..8c657f5 100644 --- a/apps/server-net/src/Modules/Chats/Infrastructure/Persistence/Migrations/20260313183634_AddStoryIdToMessages.cs +++ b/apps/server-net/src/Modules/Chats/Infrastructure/Persistence/Migrations/20260313183634_AddStoryIdToMessages.cs @@ -1,4 +1,4 @@ -using System; +using System; using Microsoft.EntityFrameworkCore.Migrations; #nullable disable diff --git a/apps/server-net/src/Modules/Chats/Infrastructure/Persistence/Migrations/20260313201532_AddStoryMediaInfoToMessages.Designer.cs b/apps/server-net/src/Modules/Chats/Infrastructure/Persistence/Migrations/20260313201532_AddStoryMediaInfoToMessages.Designer.cs index 17bbe5d..9e9d74f 100644 --- a/apps/server-net/src/Modules/Chats/Infrastructure/Persistence/Migrations/20260313201532_AddStoryMediaInfoToMessages.Designer.cs +++ b/apps/server-net/src/Modules/Chats/Infrastructure/Persistence/Migrations/20260313201532_AddStoryMediaInfoToMessages.Designer.cs @@ -1,4 +1,4 @@ -// +// using System; using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Infrastructure; diff --git a/apps/server-net/src/Modules/Chats/Infrastructure/Persistence/Migrations/20260313201532_AddStoryMediaInfoToMessages.cs b/apps/server-net/src/Modules/Chats/Infrastructure/Persistence/Migrations/20260313201532_AddStoryMediaInfoToMessages.cs index 99a8e40..7aec783 100644 --- a/apps/server-net/src/Modules/Chats/Infrastructure/Persistence/Migrations/20260313201532_AddStoryMediaInfoToMessages.cs +++ b/apps/server-net/src/Modules/Chats/Infrastructure/Persistence/Migrations/20260313201532_AddStoryMediaInfoToMessages.cs @@ -1,4 +1,4 @@ -using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Migrations; #nullable disable diff --git a/apps/server-net/src/Modules/Chats/Infrastructure/Persistence/Migrations/20260313204219_AddChatDescription.Designer.cs b/apps/server-net/src/Modules/Chats/Infrastructure/Persistence/Migrations/20260313204219_AddChatDescription.Designer.cs index cb6793f..06969ac 100644 --- a/apps/server-net/src/Modules/Chats/Infrastructure/Persistence/Migrations/20260313204219_AddChatDescription.Designer.cs +++ b/apps/server-net/src/Modules/Chats/Infrastructure/Persistence/Migrations/20260313204219_AddChatDescription.Designer.cs @@ -1,4 +1,4 @@ -// +// using System; using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Infrastructure; diff --git a/apps/server-net/src/Modules/Chats/Infrastructure/Persistence/Migrations/20260313204219_AddChatDescription.cs b/apps/server-net/src/Modules/Chats/Infrastructure/Persistence/Migrations/20260313204219_AddChatDescription.cs index 673299c..3d32323 100644 --- a/apps/server-net/src/Modules/Chats/Infrastructure/Persistence/Migrations/20260313204219_AddChatDescription.cs +++ b/apps/server-net/src/Modules/Chats/Infrastructure/Persistence/Migrations/20260313204219_AddChatDescription.cs @@ -1,4 +1,4 @@ -using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Migrations; #nullable disable diff --git a/apps/server-net/src/Modules/Chats/Infrastructure/Persistence/Migrations/ChatsDbContextModelSnapshot.cs b/apps/server-net/src/Modules/Chats/Infrastructure/Persistence/Migrations/ChatsDbContextModelSnapshot.cs index 9fdbddf..1c2ee7b 100644 --- a/apps/server-net/src/Modules/Chats/Infrastructure/Persistence/Migrations/ChatsDbContextModelSnapshot.cs +++ b/apps/server-net/src/Modules/Chats/Infrastructure/Persistence/Migrations/ChatsDbContextModelSnapshot.cs @@ -1,4 +1,4 @@ -// +// using System; using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Infrastructure; diff --git a/apps/server-net/src/Modules/Identity/Class1.cs b/apps/server-net/src/Modules/Identity/Class1.cs index 7362c82..a8baf85 100644 --- a/apps/server-net/src/Modules/Identity/Class1.cs +++ b/apps/server-net/src/Modules/Identity/Class1.cs @@ -1,4 +1,4 @@ -namespace Knot.Modules.Identity; +namespace Knot.Modules.Identity; public class Class1 { diff --git a/apps/server-net/src/Modules/Identity/Infrastructure/Persistence/IdentityDbContext.cs b/apps/server-net/src/Modules/Identity/Infrastructure/Persistence/IdentityDbContext.cs index abbac8d..b7ade0f 100644 --- a/apps/server-net/src/Modules/Identity/Infrastructure/Persistence/IdentityDbContext.cs +++ b/apps/server-net/src/Modules/Identity/Infrastructure/Persistence/IdentityDbContext.cs @@ -5,6 +5,7 @@ using Microsoft.EntityFrameworkCore.Metadata; using Knot.Modules.Identity.Application.Abstractions; using Knot.Modules.Identity.Domain; using Knot.Shared.Kernel; +using Knot.Shared.Kernel.Security; namespace Knot.Modules.Identity.Infrastructure.Persistence; @@ -14,11 +15,13 @@ namespace Knot.Modules.Identity.Infrastructure.Persistence; public sealed class IdentityDbContext : DbContext, IIdentityUnitOfWork { private readonly IMediator _mediator; + private readonly IEncryptionService _encryptionService; - public IdentityDbContext(DbContextOptions options, IMediator mediator) + public IdentityDbContext(DbContextOptions options, IMediator mediator, IEncryptionService encryptionService) : base(options) { _mediator = mediator; + _encryptionService = encryptionService; } public DbSet Users => Set(); @@ -52,6 +55,12 @@ public sealed class IdentityDbContext : DbContext, IIdentityUnitOfWork builder.HasKey(s => s.Id); builder.Property(s => s.Type).IsRequired(); + builder.Property(s => s.Content) + .HasConversion( + v => v == null ? null : _encryptionService.EncryptMessage(v), + v => v == null ? null : _encryptionService.DecryptMessage(v) + ); + // Configure backing fields for collections builder.Metadata.FindNavigation(nameof(Story.Viewers))?.SetPropertyAccessMode(PropertyAccessMode.Field); @@ -92,7 +101,14 @@ public sealed class IdentityDbContext : DbContext, IIdentityUnitOfWork { builder.ToTable("StoryReplies"); builder.HasKey(r => r.Id); - builder.Property(r => r.Content).IsRequired().HasMaxLength(500); + + builder.Property(r => r.Content) + .IsRequired() + .HasMaxLength(500) + .HasConversion( + v => v == null ? null : _encryptionService.EncryptMessage(v), + v => v == null ? null : _encryptionService.DecryptMessage(v) + ); }); modelBuilder.Entity(builder => diff --git a/apps/server-net/src/Modules/Identity/Infrastructure/Persistence/Migrations/20260311180816_InitialIdentity.Designer.cs b/apps/server-net/src/Modules/Identity/Infrastructure/Persistence/Migrations/20260311180816_InitialIdentity.Designer.cs index a44dcbd..ac05526 100644 --- a/apps/server-net/src/Modules/Identity/Infrastructure/Persistence/Migrations/20260311180816_InitialIdentity.Designer.cs +++ b/apps/server-net/src/Modules/Identity/Infrastructure/Persistence/Migrations/20260311180816_InitialIdentity.Designer.cs @@ -1,4 +1,4 @@ -// +// using System; using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Infrastructure; diff --git a/apps/server-net/src/Modules/Identity/Infrastructure/Persistence/Migrations/20260311180816_InitialIdentity.cs b/apps/server-net/src/Modules/Identity/Infrastructure/Persistence/Migrations/20260311180816_InitialIdentity.cs index 6ad3748..7086701 100644 --- a/apps/server-net/src/Modules/Identity/Infrastructure/Persistence/Migrations/20260311180816_InitialIdentity.cs +++ b/apps/server-net/src/Modules/Identity/Infrastructure/Persistence/Migrations/20260311180816_InitialIdentity.cs @@ -1,4 +1,4 @@ -using System; +using System; using Microsoft.EntityFrameworkCore.Migrations; #nullable disable diff --git a/apps/server-net/src/Modules/Identity/Infrastructure/Persistence/Migrations/20260311200839_UpdateIdentityWithNewFieldsAndTables.Designer.cs b/apps/server-net/src/Modules/Identity/Infrastructure/Persistence/Migrations/20260311200839_UpdateIdentityWithNewFieldsAndTables.Designer.cs index 3730c3b..dcb0491 100644 --- a/apps/server-net/src/Modules/Identity/Infrastructure/Persistence/Migrations/20260311200839_UpdateIdentityWithNewFieldsAndTables.Designer.cs +++ b/apps/server-net/src/Modules/Identity/Infrastructure/Persistence/Migrations/20260311200839_UpdateIdentityWithNewFieldsAndTables.Designer.cs @@ -1,4 +1,4 @@ -// +// using System; using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Infrastructure; diff --git a/apps/server-net/src/Modules/Identity/Infrastructure/Persistence/Migrations/20260311200839_UpdateIdentityWithNewFieldsAndTables.cs b/apps/server-net/src/Modules/Identity/Infrastructure/Persistence/Migrations/20260311200839_UpdateIdentityWithNewFieldsAndTables.cs index f7a178c..8c448a7 100644 --- a/apps/server-net/src/Modules/Identity/Infrastructure/Persistence/Migrations/20260311200839_UpdateIdentityWithNewFieldsAndTables.cs +++ b/apps/server-net/src/Modules/Identity/Infrastructure/Persistence/Migrations/20260311200839_UpdateIdentityWithNewFieldsAndTables.cs @@ -1,4 +1,4 @@ -using System; +using System; using Microsoft.EntityFrameworkCore.Migrations; #nullable disable diff --git a/apps/server-net/src/Modules/Identity/Infrastructure/Persistence/Migrations/20260311215347_AddHideStoryViewsToUser.Designer.cs b/apps/server-net/src/Modules/Identity/Infrastructure/Persistence/Migrations/20260311215347_AddHideStoryViewsToUser.Designer.cs index dc4e7f5..1022027 100644 --- a/apps/server-net/src/Modules/Identity/Infrastructure/Persistence/Migrations/20260311215347_AddHideStoryViewsToUser.Designer.cs +++ b/apps/server-net/src/Modules/Identity/Infrastructure/Persistence/Migrations/20260311215347_AddHideStoryViewsToUser.Designer.cs @@ -1,4 +1,4 @@ -// +// using System; using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Infrastructure; diff --git a/apps/server-net/src/Modules/Identity/Infrastructure/Persistence/Migrations/20260311215347_AddHideStoryViewsToUser.cs b/apps/server-net/src/Modules/Identity/Infrastructure/Persistence/Migrations/20260311215347_AddHideStoryViewsToUser.cs index 3df6ef5..64f0280 100644 --- a/apps/server-net/src/Modules/Identity/Infrastructure/Persistence/Migrations/20260311215347_AddHideStoryViewsToUser.cs +++ b/apps/server-net/src/Modules/Identity/Infrastructure/Persistence/Migrations/20260311215347_AddHideStoryViewsToUser.cs @@ -1,4 +1,4 @@ -using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Migrations; #nullable disable diff --git a/apps/server-net/src/Modules/Identity/Infrastructure/Persistence/Migrations/20260312174453_AddHideStoryViewsAndFixStoryViewer.Designer.cs b/apps/server-net/src/Modules/Identity/Infrastructure/Persistence/Migrations/20260312174453_AddHideStoryViewsAndFixStoryViewer.Designer.cs index 2d03a73..99d5445 100644 --- a/apps/server-net/src/Modules/Identity/Infrastructure/Persistence/Migrations/20260312174453_AddHideStoryViewsAndFixStoryViewer.Designer.cs +++ b/apps/server-net/src/Modules/Identity/Infrastructure/Persistence/Migrations/20260312174453_AddHideStoryViewsAndFixStoryViewer.Designer.cs @@ -1,4 +1,4 @@ -// +// using System; using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Infrastructure; diff --git a/apps/server-net/src/Modules/Identity/Infrastructure/Persistence/Migrations/20260312174453_AddHideStoryViewsAndFixStoryViewer.cs b/apps/server-net/src/Modules/Identity/Infrastructure/Persistence/Migrations/20260312174453_AddHideStoryViewsAndFixStoryViewer.cs index b719aef..32d71ef 100644 --- a/apps/server-net/src/Modules/Identity/Infrastructure/Persistence/Migrations/20260312174453_AddHideStoryViewsAndFixStoryViewer.cs +++ b/apps/server-net/src/Modules/Identity/Infrastructure/Persistence/Migrations/20260312174453_AddHideStoryViewsAndFixStoryViewer.cs @@ -1,4 +1,4 @@ -using System; +using System; using Microsoft.EntityFrameworkCore.Migrations; #nullable disable diff --git a/apps/server-net/src/Modules/Identity/Infrastructure/Persistence/Migrations/20260313115319_AddStoryReactionsReplies.Designer.cs b/apps/server-net/src/Modules/Identity/Infrastructure/Persistence/Migrations/20260313115319_AddStoryReactionsReplies.Designer.cs index afbf1d7..da3c592 100644 --- a/apps/server-net/src/Modules/Identity/Infrastructure/Persistence/Migrations/20260313115319_AddStoryReactionsReplies.Designer.cs +++ b/apps/server-net/src/Modules/Identity/Infrastructure/Persistence/Migrations/20260313115319_AddStoryReactionsReplies.Designer.cs @@ -1,4 +1,4 @@ -// +// using System; using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Infrastructure; diff --git a/apps/server-net/src/Modules/Identity/Infrastructure/Persistence/Migrations/20260313115319_AddStoryReactionsReplies.cs b/apps/server-net/src/Modules/Identity/Infrastructure/Persistence/Migrations/20260313115319_AddStoryReactionsReplies.cs index 169028c..8069b35 100644 --- a/apps/server-net/src/Modules/Identity/Infrastructure/Persistence/Migrations/20260313115319_AddStoryReactionsReplies.cs +++ b/apps/server-net/src/Modules/Identity/Infrastructure/Persistence/Migrations/20260313115319_AddStoryReactionsReplies.cs @@ -1,4 +1,4 @@ -using System; +using System; using Microsoft.EntityFrameworkCore.Migrations; #nullable disable diff --git a/apps/server-net/src/Modules/Identity/Infrastructure/Persistence/Migrations/IdentityDbContextModelSnapshot.cs b/apps/server-net/src/Modules/Identity/Infrastructure/Persistence/Migrations/IdentityDbContextModelSnapshot.cs index 486ef99..f1e727c 100644 --- a/apps/server-net/src/Modules/Identity/Infrastructure/Persistence/Migrations/IdentityDbContextModelSnapshot.cs +++ b/apps/server-net/src/Modules/Identity/Infrastructure/Persistence/Migrations/IdentityDbContextModelSnapshot.cs @@ -1,4 +1,4 @@ -// +// using System; using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Infrastructure; diff --git a/apps/server-net/src/Shared/Knot.Shared.Infrastructure/Class1.cs b/apps/server-net/src/Shared/Knot.Shared.Infrastructure/Class1.cs index 5d6448e..8f00c44 100644 --- a/apps/server-net/src/Shared/Knot.Shared.Infrastructure/Class1.cs +++ b/apps/server-net/src/Shared/Knot.Shared.Infrastructure/Class1.cs @@ -1,4 +1,4 @@ -namespace Knot.Shared.Infrastructure; +namespace Knot.Shared.Infrastructure; public class Class1 { diff --git a/apps/server-net/src/Shared/Knot.Shared.Infrastructure/DependencyInjection.cs b/apps/server-net/src/Shared/Knot.Shared.Infrastructure/DependencyInjection.cs index 96cd790..30110d0 100644 --- a/apps/server-net/src/Shared/Knot.Shared.Infrastructure/DependencyInjection.cs +++ b/apps/server-net/src/Shared/Knot.Shared.Infrastructure/DependencyInjection.cs @@ -1,15 +1,45 @@ using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Configuration; using Knot.Shared.Kernel; +using Knot.Shared.Kernel.Security; +using Knot.Shared.Kernel.Storage; +using Minio; +using System; namespace Knot.Shared.Infrastructure; public static class DependencyInjection { - public static IServiceCollection AddSharedInfrastructure(this IServiceCollection services) + public static IServiceCollection AddSharedInfrastructure(this IServiceCollection services, IConfiguration configuration) { services.AddHttpContextAccessor(); services.AddScoped(); + + // Настройка шифрования + var masterKey = configuration["KNOT_MASTER_ENCRYPTION_KEY"] + ?? throw new ArgumentNullException("KNOT_MASTER_ENCRYPTION_KEY is missing in env."); + services.AddSingleton(new AesEncryptionService(masterKey)); + + // Настройка MinIO (S3) + var s3Endpoint = configuration["S3_ENDPOINT"] ?? "minio:9000"; + var s3AccessKey = configuration["S3_ACCESS_KEY"] ?? "admin"; + var s3SecretKey = configuration["S3_SECRET_KEY"] ?? "KnotSuperSecretMinioPassword"; + var s3Bucket = configuration["S3_BUCKET"] ?? "knot-uploads"; + + services.AddMinio(configureClient => configureClient + .WithEndpoint(s3Endpoint) + .WithCredentials(s3AccessKey, s3SecretKey) + .WithSSL(false) + .Build()); + + services.AddScoped(provider => + { + var minioClient = provider.GetRequiredService(); + var encService = provider.GetRequiredService(); + return new S3FileStorageService(minioClient, encService, s3Bucket); + }); + return services; } } diff --git a/apps/server-net/src/Shared/Knot.Shared.Infrastructure/Knot.Shared.Infrastructure.csproj b/apps/server-net/src/Shared/Knot.Shared.Infrastructure/Knot.Shared.Infrastructure.csproj index 8d40ccb..6dd0df2 100644 --- a/apps/server-net/src/Shared/Knot.Shared.Infrastructure/Knot.Shared.Infrastructure.csproj +++ b/apps/server-net/src/Shared/Knot.Shared.Infrastructure/Knot.Shared.Infrastructure.csproj @@ -12,6 +12,7 @@ + diff --git a/apps/server-net/src/Shared/Knot.Shared.Infrastructure/Security/AesEncryptionService.cs b/apps/server-net/src/Shared/Knot.Shared.Infrastructure/Security/AesEncryptionService.cs new file mode 100644 index 0000000..9545213 --- /dev/null +++ b/apps/server-net/src/Shared/Knot.Shared.Infrastructure/Security/AesEncryptionService.cs @@ -0,0 +1,106 @@ +using System; +using System.IO; +using System.Security.Cryptography; +using System.Text; + +namespace Knot.Shared.Kernel.Security; + +public class AesEncryptionService : IEncryptionService +{ + private readonly byte[] _key; + + public AesEncryptionService(string masterKeyBase64) + { + // Try decoding as base64 or just using UTF8 string + if (masterKeyBase64.Length == 32) + { + _key = Encoding.UTF8.GetBytes(masterKeyBase64); + } + else + { + try + { + _key = Convert.FromBase64String(masterKeyBase64); + } + catch + { + _key = Encoding.UTF8.GetBytes(masterKeyBase64.PadRight(32, '0')[..32]); + } + } + + if (_key.Length != 32) + throw new ArgumentException("Мастер-ключ должен быть ровно 32 байта для AES-256."); + } + + // Сообщения: AES-256-GCM + public string EncryptMessage(string plainText) + { + if (string.IsNullOrEmpty(plainText)) return plainText; + + var nonce = new byte[12]; + RandomNumberGenerator.Fill(nonce); + + var plainBytes = Encoding.UTF8.GetBytes(plainText); + var cipherBytes = new byte[plainBytes.Length]; + var tag = new byte[16]; + + using var aesGcm = new AesGcm(_key, 16); + aesGcm.Encrypt(nonce, plainBytes, cipherBytes, tag); + + // формат: nonce:tag:cipherText + return $"{Convert.ToBase64String(nonce)}:{Convert.ToBase64String(tag)}:{Convert.ToBase64String(cipherBytes)}"; + } + + public string DecryptMessage(string cipherText) + { + if (string.IsNullOrEmpty(cipherText)) return cipherText; + + var parts = cipherText.Split(':'); + if (parts.Length != 3) return cipherText; + + try + { + var nonce = Convert.FromBase64String(parts[0]); + var tag = Convert.FromBase64String(parts[1]); + var cipherBytes = Convert.FromBase64String(parts[2]); + var plainBytes = new byte[cipherBytes.Length]; + + using var aesGcm = new AesGcm(_key, 16); + aesGcm.Decrypt(nonce, cipherBytes, tag, plainBytes); + + return Encoding.UTF8.GetString(plainBytes); + } + catch + { + return cipherText; // Возвращаем оригинал при ошибке расшифровки (например, старые не зашифрованные сообщения) + } + } + + // Потоковые файлы: AES CBC через CryptoStream + // (Примечание: Для GCM через CryptoStream нет поддержки "из коробки" в .NET при использовании Aes.Create(). + // Требуется CBC+HMAC/CTR или механизм AEAD блоками. Здесь предоставляется стандартный AES-CBC для соответствия требованиям CryptoStream). + public Stream CreateEncryptionStream(Stream unencryptedOutputStream, out byte[] iv) + { + var aes = Aes.Create(); + aes.KeySize = 256; + aes.Key = _key; + aes.Mode = CipherMode.CBC; + aes.GenerateIV(); + iv = aes.IV; + + var encryptor = aes.CreateEncryptor(aes.Key, aes.IV); + return new CryptoStream(unencryptedOutputStream, encryptor, CryptoStreamMode.Write); + } + + public Stream CreateDecryptionStream(Stream encryptedInputStream, byte[] iv) + { + var aes = Aes.Create(); + aes.KeySize = 256; + aes.Key = _key; + aes.Mode = CipherMode.CBC; + aes.IV = iv; + + var decryptor = aes.CreateDecryptor(aes.Key, aes.IV); + return new CryptoStream(encryptedInputStream, decryptor, CryptoStreamMode.Read); + } +} diff --git a/apps/server-net/src/Shared/Knot.Shared.Infrastructure/Storage/S3FileStorageService.cs b/apps/server-net/src/Shared/Knot.Shared.Infrastructure/Storage/S3FileStorageService.cs new file mode 100644 index 0000000..6a07548 --- /dev/null +++ b/apps/server-net/src/Shared/Knot.Shared.Infrastructure/Storage/S3FileStorageService.cs @@ -0,0 +1,152 @@ +using Minio; +using Minio.DataModel.Args; +using Minio.Exceptions; +using System; +using System.IO; +using System.Security.Cryptography; +using System.Text.Json; +using System.Threading.Tasks; +using Knot.Shared.Kernel.Security; + +namespace Knot.Shared.Kernel.Storage; + +public class S3FileStorageService : IFileStorageService +{ + private readonly IMinioClient _minioClient; + private readonly IEncryptionService _encryptionService; + private readonly string _bucketName; + + public S3FileStorageService(IMinioClient minioClient, IEncryptionService encryptionService, string bucketName) + { + _minioClient = minioClient; + _encryptionService = encryptionService; + _bucketName = bucketName; + } + + private async Task EnsureBucketExistsAsync() + { + try + { + var bktExistArgs = new BucketExistsArgs().WithBucket(_bucketName); + bool found = await _minioClient.BucketExistsAsync(bktExistArgs).ConfigureAwait(false); + if (!found) + { + var mkBktArgs = new MakeBucketArgs().WithBucket(_bucketName); + await _minioClient.MakeBucketAsync(mkBktArgs).ConfigureAwait(false); + } + } + catch (MinioException e) + { + Console.WriteLine($"[Bucket] Minio Error: {e.Message}"); + } + } + + public async Task UploadFileAsync(Stream fileStream, string fileName, string contentType) + { + await EnsureBucketExistsAsync(); + + // 1. Вычисляем SHA-256 для Content-Addressable Storage (CAS) - дедупликация + string fileHash; + using (var sha256 = SHA256.Create()) + using (var msHash = new MemoryStream()) + { + var startPos = fileStream.Position; + await fileStream.CopyToAsync(msHash); + var hashBytes = sha256.ComputeHash(msHash.ToArray()); + fileHash = BitConverter.ToString(hashBytes).Replace("-", "").ToLowerInvariant(); + + // Возвращаем указатель потока в начало для последующего чтения + fileStream.Position = startPos; + } + + var ext = Path.GetExtension(fileName); + var fileId = $"{fileHash}{ext}"; + + // 2. Шифруем файл "на лету" во временный файл + // Для больших файлов мы используем временный файл, чтобы не перегружать оперативную память (RAM) + var tempFilePath = Path.GetTempFileName(); + byte[] ivParams; + + try + { + using (var tempFs = new FileStream(tempFilePath, FileMode.Create, FileAccess.Write)) + using (var cryptoStream = _encryptionService.CreateEncryptionStream(tempFs, out ivParams)) + { + await fileStream.CopyToAsync(cryptoStream); + } + + // 3. Загружаем зашифрованный файл в MinIO + using var fileToUpload = new FileStream(tempFilePath, FileMode.Open, FileAccess.Read); + var metadata = new System.Collections.Generic.Dictionary + { + { "ContentType", contentType }, + { "OriginalFileName", fileName }, + { "IV", Convert.ToBase64String(ivParams) }, + { "KeyVersion", "1" }, + { "EncryptionAlgorithm", "AES-256-CBC" } // Как реализовано в потоке AES CBC + }; + + var putObjectArgs = new PutObjectArgs() + .WithBucket(_bucketName) + .WithObject(fileId) + .WithStreamData(fileToUpload) + .WithObjectSize(fileToUpload.Length) + .WithContentType("application/octet-stream") + .WithHeaders(metadata); + + await _minioClient.PutObjectAsync(putObjectArgs).ConfigureAwait(false); + + return fileId; + } + finally + { + if (File.Exists(tempFilePath)) + { + File.Delete(tempFilePath); + } + } + } + + public async Task<(Stream Stream, string ContentType, string FileName)> DownloadFileAsync(string fileId) + { + // Для скачивания, мы транслируем из Minio во временный файл, расшифровываем и возвращаем. + var tempFilePath = Path.GetTempFileName(); + string contentType = "application/octet-stream"; + string fileName = fileId; + string ivBase64 = null; + + var statArgs = new StatObjectArgs().WithBucket(_bucketName).WithObject(fileId); + var stat = await _minioClient.StatObjectAsync(statArgs).ConfigureAwait(false); + + if (stat.MetaData.ContainsKey("Contenttype")) contentType = stat.MetaData["Contenttype"]; + if (stat.MetaData.ContainsKey("Originalfilename")) fileName = stat.MetaData["Originalfilename"]; + if (stat.MetaData.ContainsKey("Iv")) ivBase64 = stat.MetaData["Iv"]; + + // Загружаем во временный файл (так как CryptoStream требует правильного чтения/записи) + var getObjArgs = new GetObjectArgs() + .WithBucket(_bucketName) + .WithObject(fileId) + .WithCallbackStream((stream) => + { + using var fs = new FileStream(tempFilePath, FileMode.Create, FileAccess.Write); + stream.CopyTo(fs); + }); + + await _minioClient.GetObjectAsync(getObjArgs).ConfigureAwait(false); + + // Создаем поток дешифрования над байтами временного файла, оборачиваем в MemoryStream, чтобы он автоматически закрывался + // В реальном масштабируемом продакшене лучше возвращать FileStream напрямую, обернутый в crypto stream. + var msResult = new MemoryStream(); + using (var fsRead = new FileStream(tempFilePath, FileMode.Open, FileAccess.Read)) + { + var iv = Convert.FromBase64String(ivBase64); + using var cryptoStream = _encryptionService.CreateDecryptionStream(fsRead, iv); + await cryptoStream.CopyToAsync(msResult); + } + + File.Delete(tempFilePath); + msResult.Position = 0; + + return (msResult, contentType, fileName); + } +} diff --git a/apps/server-net/src/Shared/Knot.Shared.Kernel/Class1.cs b/apps/server-net/src/Shared/Knot.Shared.Kernel/Class1.cs index 4524915..bb20771 100644 --- a/apps/server-net/src/Shared/Knot.Shared.Kernel/Class1.cs +++ b/apps/server-net/src/Shared/Knot.Shared.Kernel/Class1.cs @@ -1,4 +1,4 @@ -namespace Knot.Shared.Kernel; +namespace Knot.Shared.Kernel; public class Class1 { diff --git a/apps/server-net/src/Shared/Knot.Shared.Kernel/Security/IEncryptionService.cs b/apps/server-net/src/Shared/Knot.Shared.Kernel/Security/IEncryptionService.cs new file mode 100644 index 0000000..b705164 --- /dev/null +++ b/apps/server-net/src/Shared/Knot.Shared.Kernel/Security/IEncryptionService.cs @@ -0,0 +1,16 @@ +using System.IO; + +namespace Knot.Shared.Kernel.Security; + +public interface IEncryptionService +{ + // Шифрует текст (например, сообщения) для хранения в БД + string EncryptMessage(string plainText); + string DecryptMessage(string cipherText); + + // Создает поток шифрования для потоковой передачи файлов "на лету" + Stream CreateEncryptionStream(Stream unencryptedOutputStream, out byte[] iv); + + // Создает поток дешифрования для чтения файлов "на лету" + Stream CreateDecryptionStream(Stream encryptedInputStream, byte[] iv); +} diff --git a/apps/server-net/src/Shared/Knot.Shared.Kernel/Storage/IFileStorageService.cs b/apps/server-net/src/Shared/Knot.Shared.Kernel/Storage/IFileStorageService.cs new file mode 100644 index 0000000..5b7afd6 --- /dev/null +++ b/apps/server-net/src/Shared/Knot.Shared.Kernel/Storage/IFileStorageService.cs @@ -0,0 +1,13 @@ +using System.IO; +using System.Threading.Tasks; + +namespace Knot.Shared.Kernel.Storage; + +public interface IFileStorageService +{ + // Загружает поток файла и возвращает его уникальный идентификатор (SHA256 хеш или GUID). + Task UploadFileAsync(Stream stream, string fileName, string contentType); + + // Скачивает файл и возвращает его расшифрованный поток и тип содержимого. + Task<(Stream Stream, string ContentType, string FileName)> DownloadFileAsync(string fileId); +} diff --git a/apps/web/src/components/UserProfile.tsx b/apps/web/src/components/UserProfile.tsx index 6bf94de..273ce81 100644 --- a/apps/web/src/components/UserProfile.tsx +++ b/apps/web/src/components/UserProfile.tsx @@ -367,9 +367,9 @@ export default function UserProfile({ userId, chatId, onClose, onGoToMessage, is onClick={onClose} /> diff --git a/apps/web/src/lib/i18n.ts b/apps/web/src/lib/i18n.ts index 29e4b91..7f50cae 100644 --- a/apps/web/src/lib/i18n.ts +++ b/apps/web/src/lib/i18n.ts @@ -244,12 +244,12 @@ const translations = { // Misc error: 'Ошибка', // Friends - friends: 'Друзья', - friendRequests: 'Заявки в друзья', - friendsList: 'Список друзей', - noFriends: 'Пока нет друзей', - addFriend: 'Добавить в друзья', - removeFriend: 'Удалить из друзей', + friends: 'Контакты', + friendRequests: 'Заявки в контакты', + friendsList: 'Список контактов', + noFriends: 'Пока нет контактов', + addFriend: 'Добавить в контакты', + removeFriend: 'Удалить из контактов', requestSent: 'Заявка отправлена', searchFriends: 'Поиск по @username (мин. 3 символа)', noSearchResults: 'Пользователи не найдены', @@ -502,12 +502,12 @@ const translations = { searchGifs: 'Search GIFs...', trending: 'Trending', error: 'Error', - friends: 'Friends', - friendRequests: 'Friend requests', - friendsList: 'Friends list', - noFriends: 'No friends yet', - addFriend: 'Add friend', - removeFriend: 'Remove friend', + friends: 'Contacts', + friendRequests: 'Contact requests', + friendsList: 'Contacts list', + noFriends: 'No contacts yet', + addFriend: 'Add to contacts', + removeFriend: 'Remove from contacts', requestSent: 'Request sent', searchFriends: 'Search by @username (min. 3 chars)', noSearchResults: 'No users found', diff --git a/apps/web/src/lib/socket.ts b/apps/web/src/lib/socket.ts index 41c2bf9..287f832 100644 --- a/apps/web/src/lib/socket.ts +++ b/apps/web/src/lib/socket.ts @@ -60,21 +60,16 @@ export function connectSocket(token: string): SocketCompat { connection.start() .then(() => { - console.log('[SignalR] Connected successfully'); - console.log('[SignalR] Connection ID:', connection?.connectionId); }) .catch(err => console.error('[SignalR] Connection error:', err.toString())); connection.onclose((error) => { - console.log('[SignalR] Connection closed', error); }); connection.onreconnecting((error) => { - console.log('[SignalR] Reconnecting...', error); }); connection.onreconnected((connectionId) => { - console.log('[SignalR] Reconnected, new connection ID:', connectionId); }); return socketWrapper; diff --git a/diff.txt b/diff.txt new file mode 100644 index 0000000..bb0a8b4 Binary files /dev/null and b/diff.txt differ diff --git a/docker-compose.yml b/docker-compose.yml index 962342a..8b166c6 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -27,12 +27,30 @@ services: TURN_URL: ${TURN_URL} TURN_USERNAME: ${TURN_USERNAME} TURN_PASSWORD: ${TURN_PASSWORD} + S3_ENDPOINT: ${S3_ENDPOINT} + S3_ACCESS_KEY: ${S3_ACCESS_KEY} + S3_SECRET_KEY: ${S3_SECRET_KEY} + S3_BUCKET: ${S3_BUCKET} + KNOT_MASTER_ENCRYPTION_KEY: ${KNOT_MASTER_ENCRYPTION_KEY} depends_on: - db + - minio ports: - "5059:8080" + + minio: + image: minio/minio + container_name: knot-minio + restart: always + environment: + MINIO_ROOT_USER: ${S3_ACCESS_KEY} + MINIO_ROOT_PASSWORD: ${S3_SECRET_KEY} + ports: + - "9000:9000" + - "9001:9001" + command: server /data --console-address ":9001" volumes: - - ./apps/server-net/uploads:/app/uploads + - minio_data:/data web: build: @@ -51,3 +69,4 @@ services: volumes: postgres_data: + minio_data: diff --git a/logs.txt b/logs.txt new file mode 100644 index 0000000..ba749ef Binary files /dev/null and b/logs.txt differ diff --git a/logs2.txt b/logs2.txt new file mode 100644 index 0000000..b0fa06c Binary files /dev/null and b/logs2.txt differ