From 1d35356c8ccba487e89dd3b40f1442a23590e7ff Mon Sep 17 00:00:00 2001 From: Artemy <109195690+stalcker2288969@users.noreply.github.com> Date: Sun, 5 Apr 2026 13:52:13 +0700 Subject: [PATCH] firebase sdk update + sql options --- .gitignore | 3 +- .../ConfigurationProgramExtensions.cs | 38 +++++++++++++++---- Govor.API/Controllers/ProfileController.cs | 1 - Govor.API/configs/ban_usernames.json | 1 - Govor.API/secrets/firebase-adminsdk.json | 4 +- .../Services/LocalStorageService.cs | 31 +++++++++++++-- .../Services/Medias/MediaService.cs | 12 +++++- .../Providers/FirebasePushProvider.cs | 16 +++++++- .../Repositories/PushTokenRepository.cs | 15 ++++++-- .../Repositories/UserSessionsRepository.cs | 1 - Govor.Data/Repositories/UsersRepository.cs | 2 +- 11 files changed, 99 insertions(+), 25 deletions(-) diff --git a/.gitignore b/.gitignore index 947f4f7..4042b98 100644 --- a/.gitignore +++ b/.gitignore @@ -42,9 +42,10 @@ appsettings.*.json ## JetBrains Rider .idea/ -## Logs +## Logs & other *.log logs/ +uploads/ ## Папки для публикации publish/ diff --git a/Govor.API/Common/Extensions/ConfigurationProgramExtensions.cs b/Govor.API/Common/Extensions/ConfigurationProgramExtensions.cs index 9b82f1f..6de6259 100644 --- a/Govor.API/Common/Extensions/ConfigurationProgramExtensions.cs +++ b/Govor.API/Common/Extensions/ConfigurationProgramExtensions.cs @@ -158,20 +158,42 @@ public static class ConfigurationProgramExtensions { services.AddDbContext(options => { - options - .UseMySql( - configuration.GetConnectionString(nameof(GovorDbContext)), - new MySqlServerVersion(new Version(8, 0, 21)) - ); - options.UseQueryTrackingBehavior(QueryTrackingBehavior.NoTracking); + var connectionString = configuration.GetConnectionString(nameof(GovorDbContext)); + + options.UseMySql( + connectionString, + new MySqlServerVersion(new Version(8, 0, 21)), + mySqlOptions => + { + mySqlOptions.EnableRetryOnFailure( + maxRetryCount: 5, + maxRetryDelay: TimeSpan.FromSeconds(5), + errorNumbersToAdd: null); + }); + + options.EnableSensitiveDataLogging(); + options.EnableDetailedErrors(); }); } else { services.AddDbContext(options => { - options.UseNpgsql(configuration.GetConnectionString(nameof(GovorDbContext))); - options.UseQueryTrackingBehavior(QueryTrackingBehavior.NoTracking); + options.UseNpgsql( + configuration.GetConnectionString(nameof(GovorDbContext)), + npgsqlOptions => + { + // retry for transient failures + npgsqlOptions.EnableRetryOnFailure( + maxRetryCount: 5, + maxRetryDelay: TimeSpan.FromSeconds(5), + errorCodesToAdd: null); + }); + + //options.UseQueryTrackingBehavior(QueryTrackingBehavior.NoTracking); + + options.EnableSensitiveDataLogging(); + options.EnableDetailedErrors(); }); } } diff --git a/Govor.API/Controllers/ProfileController.cs b/Govor.API/Controllers/ProfileController.cs index 23b4053..a58f72e 100644 --- a/Govor.API/Controllers/ProfileController.cs +++ b/Govor.API/Controllers/ProfileController.cs @@ -68,7 +68,6 @@ public class ProfileController : ControllerBase userId); var mediaInfo = await _mediaService.UploadMediaAsync(media); - await _profileService.SetNewIcon(userId, mediaInfo.MediaId); return Ok(mediaInfo); diff --git a/Govor.API/configs/ban_usernames.json b/Govor.API/configs/ban_usernames.json index a450312..2c54218 100644 --- a/Govor.API/configs/ban_usernames.json +++ b/Govor.API/configs/ban_usernames.json @@ -51,7 +51,6 @@ "фашист", "фашизм", "рейх", - "сс", "гестапо", "свинорез", diff --git a/Govor.API/secrets/firebase-adminsdk.json b/Govor.API/secrets/firebase-adminsdk.json index 7020bd0..7595b9b 100644 --- a/Govor.API/secrets/firebase-adminsdk.json +++ b/Govor.API/secrets/firebase-adminsdk.json @@ -1,8 +1,8 @@ { "type": "service_account", "project_id": "govor-ad008", - "private_key_id": "c86cedafc5fd26278d812d1eb04281415076b70b", - "private_key": "-----BEGIN PRIVATE KEY-----\nMIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQCxSZEVKcgorEeR\nIMobn6Fa2WDRZTwqm6A/yQ4gMJb673TvOSn7ukAZoV/IoZOR8fz84OdLzoEP0/Gu\nYtCvKdc/rsVdPVnLZZxxpk+cYbrfSstdke55E67tLjtlfF7XZn3OSQ2lRszff80z\n78DHWORaSGUb4UKrB8mnvPFZmM8lDzwmslnskQBB97nIDBxsBdUD2jw2n70kjfM5\nFgNX814PEztSqwC7nNkXl2MZxWSOP7ppjTsJrDfWGhFJ1faCS+sW3KzzI4XHMUyG\nd++Oxs7QK/jcD0/NwDMjMycRv/6WZgn2ItY+EUXza/6NMS+RikXwEiZjkfvte5IU\nkTk3Wb+nAgMBAAECggEAEAQN28QVBXue0YENURaILJ1jrjb/ivfZL7n5kTXihdPZ\nE/1tigSm4sndzdGu86nRSYUF0CbdKPFkLXVW1eQpQmMHCGfQ81uAGRJFseJwT9jB\na3gR+5UoIdwh2Ia2SK5FIjweQ/aT7oLspkr79uZUZsWQgMbGT44aDRG4GfiAih/i\nCye02cBUt6LwYTSyn4toX/Hsnoew60qLsyo6UiyE18eOrrFaK0Rcc8ia8jJ7j4K/\niKywX+PiK9z9dp95G/bpspfPkTh1XE1LEL8ArglnOLSJtbewmOrGK/PODo6EaVIU\nH4uv/0ThIkjDN5PvvZZkBXLdZ/mLyAPa2Xh5hvih0QKBgQDqkSSTAJNQG3UgH3cq\n8So4XuFbsWB4zCDqpBz+WitmKJUzYNhXyTZ3Sx7JbculNFESaX2lmeHTW/7vwa/Z\nZrt6npSlR6Ln3AitCyZRE46tBlaN3kBJvTolRLZvBS+Rq/bYedNyN8Ls18yoQcUj\nsGn1Jetjl3M8QQl7AmnOb2aclwKBgQDBfJJ34ba2LwsQTTwdbCeyCGOAyj59qQaq\nB4oGKb24SDJMbOGa7p17PSwvnRUmdNXmT9QKawBf3H2d3MWBpHW1024Ycg64ZD6Y\nLCpye7wLeNs3OPyFDfpi7uXKbpBA/cjU1uI1eqXC197VmU/BAk6TxlRijY3zK1kU\n9bA0pb2HcQKBgHBV1mg4TFR+8dbSeuWr3YZlmhOpnQP87n6w3dnKISKpqNqUNMfF\n6zmyViotVOvnZDQnJS9bxNTOKAd2gNri4kJVE+cbqZ7Ut6r3vf/yF9AxSt5iY/Ns\nlh4nDB+bIi7nZi5CGcuHfOikaLTj2p++6t+mq1Zkv6FJnFq2yyk8HK49AoGBAJ5N\nADyq4+TsWj3tItjjqxqCuH4feb8vsi8cWfWu4vTJxLU4g+BRh3DT3Lnb5/j9sB9t\ngos/fh/v8qpcfQ8TcebgY/wGHTsJcRjpUZU23OP57kihDCEEDa4xzLmxeb5ipJRW\ndt7QSJxAJ6VUeKbt70ICCvpS3CdueMSoOpDoZUdRAoGBALY0kemgKgLxHDNXZEjI\n3/ndIdea5aI2q2UpZHEdfLcCE1Fb/bVjtx9ZTLsxxm25L7sdqHqxivMxmEMhgxc4\nXzvUtA22i4FIF2g6M/31gWeFtHvJWczeit/FMsw271HxPflVuuf4cxaIcYkccJTd\nQhbyfbGJAhVBR02CeDXM3lOy\n-----END PRIVATE KEY-----\n", + "private_key_id": "dc49ff3b29359c0e65ee9c1c1e0e4e825928e16b", + "private_key": "-----BEGIN PRIVATE KEY-----\nMIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQDFUuoGpZxXiEVN\nBlfWCUEnwjyev+rcPb2oMo1jKcKJh24/cHfaToThEMO6G53CpatpoB2/TFSE3hxM\ncjSy/wmCngAmqy8xKaVeg8YL4jxLlHVormgPzP1Dmoe77YSEItf7RlR13tyNzXgj\ncHHvfaaDCffwLsUF7vTleNS9w9CF03+8VfjfT2Ggu7WChpsxWrF5EUboWltNIAbQ\nmOBWCnWRbas/sMA5ijKpcg2g8f6Xj2OdUeK+229xgtBFogMgS3/U54WCKCQtDSQo\nP7a1w93nFzL3bMs7lACKBkb00VnzSQN/FoHyQRoQrwlkr309u8ZRAK142m4yFugR\nSYnDnnXRAgMBAAECggEAIPvyKLhIQQ2DYBO+XQXhAU9jqLm1/ajIajRBFCFn5mtg\nD3SyZCiZ57FQtmxlW6Mr2xUrw81zl141ZsX0bzNYmJ/msJAO7G3kub7XPSdNIukn\nEx+MP4/pigJrzAsxCpF0fwlLlwRw47dD7NCgCwN68Q6ahmibAJnoD++5yBrSYXwk\n7a1nJQhNg5AWnXSk8vCxFyhrKzMa0yh0BtWYeMVY74rSNIlvgkw8dzZEuLdnRn7f\nL4jUbyHmCIiSGt5VLTs273YqOrN5t5UW8lJUCdL8wOMJH01uWJVzKjrRLmhp3fKp\n6NZHyLpaPWbkKo0X6+PQV84RUl5+0/CkJ469G5Zz0QKBgQDtjDmsKxXoa0i1tgRq\nEnZd4WU1aEf22zcm+nWODQoFEDFYAJLGabqTuPo1oJVEo8DeQiSRUaao+ac4HHd5\nhnieTJzD12CgLSwVwy6pJN2Sbiw0sa36V7m3s+0kqPyyv+ybFbGJR3C3IAO8c0F5\nJc0bDReXNXJs/dw1fF/GMaT4BQKBgQDUptBbUojRzR5yzhdvmAqsWVBFG0Em3QAN\nKg/9GVuhKQahc7RHom+JLEYeFVgThohyXyA7iAZ0k1Mnubxa0+KIq5NqdauIsflA\n4lZ4zLjOemUjp2OAMumYvXfoAqaKfvHx8iovfTgehf1bWdtSbKwKXCJEK/7Xtw69\nU5PqommsXQKBgQCalzkpHFkDil4lMzEoPp+ddQPCf/zlX8BCs5gxO7cbmHAkTys1\nyLTNUgIzSDAcHV10xyK5F6QYL3BtickO4z439q/Gm7GcTAl2mDETjpRLqTtHh6Ir\nW0/Xh1Z7FyZsxY4bV0DUkBOAeWs1BuTDDKCwTBLgKr4uw/69KBXEC6bKaQKBgQDP\n/SilaaBws9MoLvM1Ltyhnemo7fNXc6jtN0rUHs1EX8Agge6+cd3tqOQb4Hx4/IjQ\ndMIt9+l4c/BNVpknrT5QtqBE7DqORKSqFbbGCFZpfcUWjk3/wcxAnHB++LizlV5H\narhgInWVRTB8/PJLOAc9SsgpLW7gxicgfnKad6gr8QKBgAQIQEC9HPzdFikoxOmW\nlOYaWqDl2P294C2ESGv8ti6Boxx9PsE8k2CrFYXensvk5b4+vV9XTe67PTZ2ZGJS\nLkN77+iDbP8zCysnDWyxCAwJeEmK/eVvBtMA7Juzf3ikF5FxmwXPvSRY4oPd+2cs\neejPkHyvPWNRrZryc5e+9dSJ\n-----END PRIVATE KEY-----\n", "client_email": "firebase-adminsdk-fbsvc@govor-ad008.iam.gserviceaccount.com", "client_id": "115697205969744490044", "auth_uri": "https://accounts.google.com/o/oauth2/auth", diff --git a/Govor.Application/Services/LocalStorageService.cs b/Govor.Application/Services/LocalStorageService.cs index c7ebb3a..80a2791 100644 --- a/Govor.Application/Services/LocalStorageService.cs +++ b/Govor.Application/Services/LocalStorageService.cs @@ -49,13 +49,36 @@ public class LocalStorageService : IStorageService public async Task RemoveAsync(string url) { - var path = Path.Combine(_storagePath, url); + if (string.IsNullOrWhiteSpace(url)) + throw new ArgumentException("Invalid file url"); - if (File.Exists(path)) + var rootPath = Path.GetFullPath(_storagePath); + + var fullPath = Path.GetFullPath(Path.Combine(rootPath, url)); + + if (!fullPath.StartsWith(rootPath, StringComparison.OrdinalIgnoreCase)) + throw new UnauthorizedAccessException("Invalid file path"); + + if (!File.Exists(fullPath)) + return; + + try { - File.Delete(path); + var fileInfo = new FileInfo(fullPath); + if (fileInfo.IsReadOnly) + fileInfo.IsReadOnly = false; + + File.Delete(fullPath); } - + catch (IOException ex) + { + throw new IOException($"Failed to delete file: {fullPath}", ex); + } + catch (UnauthorizedAccessException ex) + { + throw new UnauthorizedAccessException($"No access to delete file: {fullPath}", ex); + } + await Task.CompletedTask; } } diff --git a/Govor.Application/Services/Medias/MediaService.cs b/Govor.Application/Services/Medias/MediaService.cs index 389027b..828e6e8 100644 --- a/Govor.Application/Services/Medias/MediaService.cs +++ b/Govor.Application/Services/Medias/MediaService.cs @@ -52,9 +52,17 @@ public class MediaService : IMediaService } } - public Task DeleteMediaAsync(Guid fileId) + public async Task DeleteMediaAsync(Guid mediaId) { - throw new NotImplementedException(); + var mediaFile = await _dbContext.MediaFiles + .FirstOrDefaultAsync(x => x.Id == mediaId) + ?? throw new KeyNotFoundException($"No media found by given id {mediaId}"); + + await _storageService.RemoveAsync(mediaFile.Url); + + _dbContext.MediaFiles.Remove(mediaFile); + + await _dbContext.SaveChangesAsync(); } public Task GetMediaByUrlAsync(string url) diff --git a/Govor.Application/Services/PushNotifications/Providers/FirebasePushProvider.cs b/Govor.Application/Services/PushNotifications/Providers/FirebasePushProvider.cs index 5d7ac4b..1964167 100644 --- a/Govor.Application/Services/PushNotifications/Providers/FirebasePushProvider.cs +++ b/Govor.Application/Services/PushNotifications/Providers/FirebasePushProvider.cs @@ -1,11 +1,18 @@ using FirebaseAdmin.Messaging; using Govor.Application.Interfaces.PushNotifications; using Govor.Application.Interfaces.PushNotifications.Models; +using Microsoft.Extensions.Logging; namespace Govor.Application.Services.PushNotifications.Providers; public class FirebasePushProvider : IPushNotificationProvider { + private readonly ILogger _logger; + public FirebasePushProvider(ILogger logger) + { + _logger = logger; + } + public string Name => "FCM"; public async Task SendToTokenAsync(string token, PushMessage message) @@ -21,6 +28,7 @@ public class FirebasePushProvider : IPushNotificationProvider { if (IsInvalidTokenError(ex)) { + _logger.LogError(ex, "FCM send failed"); return new SendPushResult(0, 1, [token]); } @@ -63,7 +71,12 @@ public class FirebasePushProvider : IPushNotificationProvider { if (!response.Responses[i].IsSuccess) { - failedTokens.Add(tokens[i]); + var ex = response.Responses[i].Exception; + + if (ex != null && IsInvalidTokenError(ex)) + { + failedTokens.Add(tokens[i]); // invalid + } } } @@ -75,6 +88,7 @@ public class FirebasePushProvider : IPushNotificationProvider } catch (Exception ex) { + _logger.LogError(ex, "FCM multicast failed"); return new SendPushResult(0, tokens.Count, tokens.ToList()); } } diff --git a/Govor.Data/Repositories/PushTokenRepository.cs b/Govor.Data/Repositories/PushTokenRepository.cs index 61da51b..39eeaf4 100644 --- a/Govor.Data/Repositories/PushTokenRepository.cs +++ b/Govor.Data/Repositories/PushTokenRepository.cs @@ -1,3 +1,4 @@ +using System.Security; using Govor.Core.Infrastructure.Extensions; using Govor.Core.Models.Users; using Govor.Core.Repositories.PushTokens; @@ -62,10 +63,13 @@ public class PushTokenRepository : IPushTokenRepository throw new ArgumentException("Platform cannot be empty", nameof(platform)); var existing = await _context.UserPushTokens - .FirstOrDefaultAsync(t => t.UserSessionId == sessionId); - + .FirstOrDefaultAsync(t => t.UserSessionId == sessionId && t.Platform == platform); + if (existing != null) { + if(existing.UserId != userId) + throw new SecurityException("Token already belongs to another user"); + // Updates existing.UserId = userId; existing.UserSessionId = sessionId; @@ -113,7 +117,12 @@ public class PushTokenRepository : IPushTokenRepository if (toRemove.Any()) { - _context.UserPushTokens.RemoveRange(toRemove); + foreach (var token in toRemove) + { + token.IsActive = false; + _context.UserPushTokens.Update(token); + } + await _context.SaveChangesAsync(); } } diff --git a/Govor.Data/Repositories/UserSessionsRepository.cs b/Govor.Data/Repositories/UserSessionsRepository.cs index 229c108..92b68a5 100644 --- a/Govor.Data/Repositories/UserSessionsRepository.cs +++ b/Govor.Data/Repositories/UserSessionsRepository.cs @@ -95,7 +95,6 @@ public class UserSessionsRepository : IUserSessionsRepository .SetProperty(a => a.UserId, userSession.UserId) .SetProperty(u => u.RefreshTokenHash, userSession.RefreshTokenHash) .SetProperty(u => u.DeviceInfo, userSession.DeviceInfo) - .SetProperty(u => u.CreatedAt, userSession.CreatedAt) .SetProperty(u => u.ExpiresAt, userSession.ExpiresAt) .SetProperty(u => u.IsRevoked, userSession.IsRevoked) ); diff --git a/Govor.Data/Repositories/UsersRepository.cs b/Govor.Data/Repositories/UsersRepository.cs index 1a1c1a4..6cab480 100644 --- a/Govor.Data/Repositories/UsersRepository.cs +++ b/Govor.Data/Repositories/UsersRepository.cs @@ -84,7 +84,7 @@ public class UsersRepository : IUsersRepository !_context.Friendships.Any(f => ((f.RequesterId == currentUserId && f.AddresseeId == u.Id) || (f.RequesterId == u.Id && f.AddresseeId == currentUserId)) && f.Status != FriendshipStatus.Rejected)) - .Take(10) + .Take(7) .OrderBy(u => u.Username) .ToListOrThrowIfEmpty(new NotFoundByKeyException<(string, Guid)>((query, currentUserId), $"Users with given query for user {currentUserId} not found")); }