diff --git a/Crypter.Common.Client/Interfaces/Services/UserSettings/IUserTransferSettingsService.cs b/Crypter.Common.Client/Interfaces/Services/UserSettings/IUserTransferSettingsService.cs index 96c741c4..c32fe6a9 100644 --- a/Crypter.Common.Client/Interfaces/Services/UserSettings/IUserTransferSettingsService.cs +++ b/Crypter.Common.Client/Interfaces/Services/UserSettings/IUserTransferSettingsService.cs @@ -34,6 +34,7 @@ public interface IUserTransferSettingsService { Task> GetTransferSettingsAsync(); Task GetAbsoluteMaximumUploadSizeAsync(); + Task GetAbsoluteMaximumMessageLengthAsync(); Task GetCurrentMaximumUploadSizeAsync(); Task IsUserQuotaReachedAsync(); Task IsFreeTransferQuotaReachedAsync(); diff --git a/Crypter.Common.Client/Services/UserSettings/UserTransferSettingsService.cs b/Crypter.Common.Client/Services/UserSettings/UserTransferSettingsService.cs index 2eff36d2..56232dd1 100644 --- a/Crypter.Common.Client/Services/UserSettings/UserTransferSettingsService.cs +++ b/Crypter.Common.Client/Services/UserSettings/UserTransferSettingsService.cs @@ -36,7 +36,7 @@ namespace Crypter.Common.Client.Services.UserSettings; -public class UserTransferSettingsService : IUserTransferSettingsService +public class UserTransferSettingsService : IUserTransferSettingsService, IDisposable { private readonly IUserSessionService _userSessionService; private readonly ICrypterApiClient _crypterApiClient; @@ -44,20 +44,24 @@ public class UserTransferSettingsService : IUserTransferSettingsService private readonly IMemoryCache _memoryCache; private readonly SemaphoreSlim _memoryCacheLock = new SemaphoreSlim(1, 1); + private const string TransferSettingsCacheKey = $"{nameof(UserTransferSettingsService)}:TransferSettings"; + public UserTransferSettingsService(IUserSessionService userSessionService, ICrypterApiClient crypterApiClient, IMemoryCache memoryCache) { _userSessionService = userSessionService; _crypterApiClient = crypterApiClient; _memoryCache = memoryCache; + + userSessionService.UserLoggedInEventHandler += RecycleAsync; + userSessionService.UserLoggedOutEventHandler += RecycleAsync; } public async Task> GetTransferSettingsAsync() { - const string cacheKey = $"{nameof(UserTransferSettingsService)}:{nameof(GetTransferSettingsAsync)}"; try { await _memoryCacheLock.WaitAsync(); - return await _memoryCache.GetOrCreateAsync(cacheKey, async entry => + return await _memoryCache.GetOrCreateAsync(TransferSettingsCacheKey, async entry => { bool isLoggedIn = await _userSessionService.IsLoggedInAsync(); Maybe uploadSettings = await _crypterApiClient.UserSetting.GetTransferSettingsAsync(isLoggedIn); @@ -76,32 +80,55 @@ public async Task> GetTransferSettingsAsync() public async Task GetAbsoluteMaximumUploadSizeAsync() { return await GetTransferSettingsAsync() - .MatchAsync( - () => 0, - x => x.MaximumUploadSize); + .Select(x => x.MaximumUploadSize) + .SomeOrDefaultAsync(0); + } + + public async Task GetAbsoluteMaximumMessageLengthAsync() + { + return await GetTransferSettingsAsync() + .Select(x => x.MaximumMessageLength) + .SomeOrDefaultAsync(0); } public async Task GetCurrentMaximumUploadSizeAsync() { return await GetTransferSettingsAsync() - .MatchAsync( - () => 0, - x => Math.Min(x.MaximumUploadSize, Math.Min(x.AvailableUserSpace, x.AvailableFreeTransferSpace))); + .Select(x => Math.Min(x.MaximumUploadSize, Math.Min(x.AvailableUserSpace, x.AvailableFreeTransferSpace))) + .SomeOrDefaultAsync(0); } public async Task IsUserQuotaReachedAsync() { return await GetTransferSettingsAsync() - .MatchAsync( - () => true, - x => x.AvailableUserSpace == 0); + .Select(x => x.AvailableUserSpace == 0) + .SomeOrDefaultAsync(true); } public async Task IsFreeTransferQuotaReachedAsync() { return await GetTransferSettingsAsync() - .MatchAsync( - () => true, - x => x.AvailableFreeTransferSpace == 0); + .Select(x => x.AvailableFreeTransferSpace == 0) + .SomeOrDefaultAsync(true); + } + + private async void RecycleAsync(object? _, EventArgs __) + { + try + { + await _memoryCacheLock.WaitAsync(); + _memoryCache.Remove(TransferSettingsCacheKey); + } + finally + { + _memoryCacheLock.Release(); + } + } + + public void Dispose() + { + _userSessionService.UserLoggedInEventHandler -= RecycleAsync; + _userSessionService.UserLoggedOutEventHandler -= RecycleAsync; + GC.SuppressFinalize(this); } } diff --git a/Crypter.Common/Contracts/Features/UserSettings/TransferSettings/GetTransferSettingsResponse.cs b/Crypter.Common/Contracts/Features/UserSettings/TransferSettings/GetTransferSettingsResponse.cs index 9726f8d3..5f8f34ce 100644 --- a/Crypter.Common/Contracts/Features/UserSettings/TransferSettings/GetTransferSettingsResponse.cs +++ b/Crypter.Common/Contracts/Features/UserSettings/TransferSettings/GetTransferSettingsResponse.cs @@ -30,6 +30,7 @@ public sealed record GetTransferSettingsResponse { public string TierName { get; set; } public long MaximumUploadSize { get; init; } + public int MaximumMessageLength { get; init; } public long AvailableUserSpace { get; init; } public long UsedUserSpace { get; init; } public long UserQuota { get; init; } @@ -37,10 +38,11 @@ public sealed record GetTransferSettingsResponse public long UsedFreeTransferSpace { get; init; } public long FreeTransferQuota { get; init; } - public GetTransferSettingsResponse(string tierName, long maximumUploadSize, long availableUserSpace, long usedUserSpace, long userQuota, long availableFreeTransferSpace, long usedFreeTransferSpace, long freeTransferQuota) + public GetTransferSettingsResponse(string tierName, long maximumUploadSize, int maximumMessageLength, long availableUserSpace, long usedUserSpace, long userQuota, long availableFreeTransferSpace, long usedFreeTransferSpace, long freeTransferQuota) { TierName = tierName; MaximumUploadSize = maximumUploadSize; + MaximumMessageLength = maximumMessageLength; AvailableUserSpace = availableUserSpace; UsedUserSpace = usedUserSpace; UserQuota = userQuota; diff --git a/Crypter.Core/Features/Transfer/Commands/SaveFileTransferCommand.cs b/Crypter.Core/Features/Transfer/Commands/SaveFileTransferCommand.cs index 65e89384..ac867577 100644 --- a/Crypter.Core/Features/Transfer/Commands/SaveFileTransferCommand.cs +++ b/Crypter.Core/Features/Transfer/Commands/SaveFileTransferCommand.cs @@ -58,7 +58,6 @@ internal class SaveFileTransferCommandHandler private readonly IHashIdService _hashIdService; private readonly IPublisher _publisher; private readonly ITransferRepository _transferRepository; - private readonly TransferStorageSettings _transferStorageSettings; public SaveFileTransferCommandHandler( DataContext dataContext, @@ -71,7 +70,6 @@ public SaveFileTransferCommandHandler( _hashIdService = hashIdService; _publisher = publisher; _transferRepository = transferRepository; - _transferStorageSettings = transferStorageSettings.Value; } public async Task> Handle(SaveFileTransferCommand request, CancellationToken cancellationToken) diff --git a/Crypter.Core/Features/Transfer/Commands/SaveMessageTransferCommand.cs b/Crypter.Core/Features/Transfer/Commands/SaveMessageTransferCommand.cs index 44e1cf91..b01d7c3c 100644 --- a/Crypter.Core/Features/Transfer/Commands/SaveMessageTransferCommand.cs +++ b/Crypter.Core/Features/Transfer/Commands/SaveMessageTransferCommand.cs @@ -58,7 +58,6 @@ internal class SaveMessageTransferCommandHandler private readonly IHashIdService _hashIdService; private readonly IPublisher _publisher; private readonly ITransferRepository _transferRepository; - private readonly TransferStorageSettings _transferStorageSettings; public SaveMessageTransferCommandHandler( DataContext dataContext, @@ -71,7 +70,6 @@ public SaveMessageTransferCommandHandler( _hashIdService = hashIdService; _publisher = publisher; _transferRepository = transferRepository; - _transferStorageSettings = transferStorageSettings.Value; } public async Task> Handle(SaveMessageTransferCommand request, CancellationToken cancellationToken) diff --git a/Crypter.Core/Features/Transfer/Common.cs b/Crypter.Core/Features/Transfer/Common.cs index c993ef5f..377e2cce 100644 --- a/Crypter.Core/Features/Transfer/Common.cs +++ b/Crypter.Core/Features/Transfer/Common.cs @@ -178,8 +178,7 @@ private static async Task> GetUploadRecipientIdAsync( private static async Task HasSpaceForTransferAsync(DataContext dataContext, Maybe possibleUserId, long ciphertextStreamLength, CancellationToken cancellationToken = default) { return await UserSettings.Common.GetUserTransferSettingsAsync(dataContext, possibleUserId, cancellationToken) - .MatchAsync( - () => false, - x => Math.Min(x.MaximumUploadSize, Math.Min(x.AvailableFreeTransferSpace, x.AvailableUserSpace)) >= ciphertextStreamLength); + .Select(x => Math.Min(x.MaximumUploadSize, Math.Min(x.AvailableFreeTransferSpace, x.AvailableUserSpace)) >= ciphertextStreamLength) + .SomeOrDefaultAsync(false); } } diff --git a/Crypter.Core/Features/UserSettings/Common.cs b/Crypter.Core/Features/UserSettings/Common.cs index b5e7f08a..2911b18a 100644 --- a/Crypter.Core/Features/UserSettings/Common.cs +++ b/Crypter.Core/Features/UserSettings/Common.cs @@ -90,6 +90,7 @@ internal static async Task> GetUserTransferSe { x.Name, x.MaximumUploadSize, + x.MaximumMessageLength, x.UserQuota, UsedUserSpace = x.DefaultForUserCategory == UserCategory.Anonymous ? dataContext.AnonymousFileTransfers @@ -124,6 +125,6 @@ internal static async Task> GetUserTransferSe return data is null ? Maybe.None - : new GetTransferSettingsResponse(data.Name, data.MaximumUploadSize, data.UserQuota - data.UsedUserSpace, data.UsedUserSpace, data.UserQuota, data.FreeTransferQuota - data.UsedFreeTransferSpace, data.UsedFreeTransferSpace, data.FreeTransferQuota); + : new GetTransferSettingsResponse(data.Name, data.MaximumUploadSize, data.MaximumMessageLength, data.UserQuota - data.UsedUserSpace, data.UsedUserSpace, data.UserQuota, data.FreeTransferQuota - data.UsedFreeTransferSpace, data.UsedFreeTransferSpace, data.FreeTransferQuota); } } diff --git a/Crypter.DataAccess/Entities/TransferTierEntity.cs b/Crypter.DataAccess/Entities/TransferTierEntity.cs index 4b5b5772..68ed6c9f 100644 --- a/Crypter.DataAccess/Entities/TransferTierEntity.cs +++ b/Crypter.DataAccess/Entities/TransferTierEntity.cs @@ -36,6 +36,7 @@ public class TransferTierEntity public string Name { get; set; } = null!; public string? Description { get; set; } public long MaximumUploadSize { get; set; } + public int MaximumMessageLength { get; set; } public long UserQuota { get; set; } public UserCategory? DefaultForUserCategory { get; set; } } diff --git a/Crypter.DataAccess/Migrations/20250424020928_AddMaximumMessageLengthColumn.Designer.cs b/Crypter.DataAccess/Migrations/20250424020928_AddMaximumMessageLengthColumn.Designer.cs new file mode 100644 index 00000000..4c86a456 --- /dev/null +++ b/Crypter.DataAccess/Migrations/20250424020928_AddMaximumMessageLengthColumn.Designer.cs @@ -0,0 +1,796 @@ +// +using System; +using System.Text.Json; +using Crypter.DataAccess; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; + +#nullable disable + +namespace Crypter.DataAccess.Migrations +{ + [DbContext(typeof(DataContext))] + [Migration("20250424020928_AddMaximumMessageLengthColumn")] + partial class AddMaximumMessageLengthColumn + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasDefaultSchema("crypter") + .HasAnnotation("ProductVersion", "9.0.3") + .HasAnnotation("Relational:MaxIdentifierLength", 63); + + NpgsqlModelBuilderExtensions.HasPostgresExtension(modelBuilder, "citext"); + NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); + + modelBuilder.Entity("Crypter.DataAccess.Entities.AnonymousFileTransferEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("ContentType") + .IsRequired() + .HasColumnType("text"); + + b.Property("Created") + .HasColumnType("timestamp with time zone"); + + b.Property("Expiration") + .HasColumnType("timestamp with time zone"); + + b.Property("FileName") + .IsRequired() + .HasColumnType("text"); + + b.Property("KeyExchangeNonce") + .IsRequired() + .HasColumnType("bytea"); + + b.Property("Parts") + .ValueGeneratedOnAdd() + .HasColumnType("boolean") + .HasDefaultValue(false); + + b.Property("Proof") + .IsRequired() + .HasColumnType("bytea"); + + b.Property("PublicKey") + .IsRequired() + .HasColumnType("bytea"); + + b.Property("Size") + .HasColumnType("bigint"); + + b.HasKey("Id"); + + b.ToTable("AnonymousFileTransfer", "crypter"); + }); + + modelBuilder.Entity("Crypter.DataAccess.Entities.AnonymousMessageTransferEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Created") + .HasColumnType("timestamp with time zone"); + + b.Property("Expiration") + .HasColumnType("timestamp with time zone"); + + b.Property("KeyExchangeNonce") + .IsRequired() + .HasColumnType("bytea"); + + b.Property("Proof") + .IsRequired() + .HasColumnType("bytea"); + + b.Property("PublicKey") + .IsRequired() + .HasColumnType("bytea"); + + b.Property("Size") + .HasColumnType("bigint"); + + b.Property("Subject") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.ToTable("AnonymousMessageTransfer", "crypter"); + }); + + modelBuilder.Entity("Crypter.DataAccess.Entities.ApplicationSettingEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("FreeTransferQuota") + .HasColumnType("bigint"); + + b.HasKey("Id"); + + b.ToTable("ApplicationSetting", "crypter"); + }); + + modelBuilder.Entity("Crypter.DataAccess.Entities.EventLogEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("AdditionalData") + .IsRequired() + .HasColumnType("jsonb"); + + b.Property("EventLogType") + .HasColumnType("integer"); + + b.Property("Timestamp") + .HasColumnType("timestamp with time zone"); + + b.HasKey("Id"); + + b.HasIndex("EventLogType"); + + b.ToTable("EventLog", "crypter"); + }); + + modelBuilder.Entity("Crypter.DataAccess.Entities.TransferTierEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityAlwaysColumn(b.Property("Id")); + + b.Property("DefaultForUserCategory") + .HasColumnType("integer"); + + b.Property("Description") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("MaximumMessageLength") + .HasColumnType("integer"); + + b.Property("MaximumUploadSize") + .HasColumnType("bigint"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(64) + .HasColumnType("character varying(64)"); + + b.Property("UserQuota") + .HasColumnType("bigint"); + + b.HasKey("Id"); + + b.HasIndex("DefaultForUserCategory") + .IsUnique(); + + b.ToTable("TransferTier", "crypter"); + }); + + modelBuilder.Entity("Crypter.DataAccess.Entities.UserConsentEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + NpgsqlPropertyBuilderExtensions.UseIdentityAlwaysColumn(b.Property("Id")); + + b.Property("Activated") + .HasColumnType("timestamp with time zone"); + + b.Property("Active") + .HasColumnType("boolean"); + + b.Property("ConsentType") + .HasColumnType("integer"); + + b.Property("Deactivated") + .HasColumnType("timestamp with time zone"); + + b.Property("Owner") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("Owner"); + + b.ToTable("UserConsent", "crypter"); + }); + + modelBuilder.Entity("Crypter.DataAccess.Entities.UserContactEntity", b => + { + b.Property("OwnerId") + .HasColumnType("uuid") + .HasColumnName("Owner"); + + b.Property("ContactId") + .HasColumnType("uuid") + .HasColumnName("Contact"); + + b.HasKey("OwnerId", "ContactId"); + + b.HasIndex("ContactId"); + + b.ToTable("UserContact", "crypter"); + }); + + modelBuilder.Entity("Crypter.DataAccess.Entities.UserEmailChangeEntity", b => + { + b.Property("Owner") + .HasColumnType("uuid"); + + b.Property("Code") + .HasColumnType("uuid"); + + b.Property("Created") + .HasColumnType("timestamp with time zone"); + + b.Property("EmailAddress") + .IsRequired() + .HasColumnType("citext"); + + b.Property("VerificationKey") + .HasColumnType("bytea"); + + b.Property("VerificationSent") + .HasColumnType("timestamp with time zone"); + + b.HasKey("Owner"); + + b.HasIndex("Code") + .IsUnique(); + + b.HasIndex("EmailAddress") + .IsUnique(); + + b.ToTable("UserEmailChange", "crypter"); + }); + + modelBuilder.Entity("Crypter.DataAccess.Entities.UserEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("ClientPasswordVersion") + .ValueGeneratedOnAdd() + .HasColumnType("smallint") + .HasDefaultValue((short)0); + + b.Property("Created") + .HasColumnType("timestamp with time zone"); + + b.Property("EmailAddress") + .HasColumnType("citext"); + + b.Property("LastLogin") + .HasColumnType("timestamp with time zone"); + + b.Property("PasswordHash") + .IsRequired() + .HasColumnType("bytea"); + + b.Property("PasswordSalt") + .IsRequired() + .HasColumnType("bytea"); + + b.Property("ServerPasswordVersion") + .ValueGeneratedOnAdd() + .HasColumnType("smallint") + .HasDefaultValue((short)0); + + b.Property("Username") + .IsRequired() + .HasColumnType("citext"); + + b.HasKey("Id"); + + b.HasIndex("EmailAddress") + .IsUnique(); + + b.HasIndex("Username") + .IsUnique(); + + b.ToTable("User", "crypter"); + }); + + modelBuilder.Entity("Crypter.DataAccess.Entities.UserFailedLoginEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Date") + .HasColumnType("timestamp with time zone"); + + b.Property("Owner") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("Owner"); + + b.ToTable("UserFailedLogin", "crypter"); + }); + + modelBuilder.Entity("Crypter.DataAccess.Entities.UserFileTransferEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("ContentType") + .IsRequired() + .HasColumnType("text"); + + b.Property("Created") + .HasColumnType("timestamp with time zone"); + + b.Property("Expiration") + .HasColumnType("timestamp with time zone"); + + b.Property("FileName") + .IsRequired() + .HasColumnType("text"); + + b.Property("KeyExchangeNonce") + .IsRequired() + .HasColumnType("bytea"); + + b.Property("Parts") + .ValueGeneratedOnAdd() + .HasColumnType("boolean") + .HasDefaultValue(false); + + b.Property("Proof") + .IsRequired() + .HasColumnType("bytea"); + + b.Property("PublicKey") + .HasColumnType("bytea"); + + b.Property("RecipientId") + .HasColumnType("uuid") + .HasColumnName("Recipient"); + + b.Property("SenderId") + .HasColumnType("uuid") + .HasColumnName("Sender"); + + b.Property("Size") + .HasColumnType("bigint"); + + b.HasKey("Id"); + + b.HasIndex("RecipientId"); + + b.HasIndex("SenderId"); + + b.ToTable("UserFileTransfer", "crypter"); + }); + + modelBuilder.Entity("Crypter.DataAccess.Entities.UserKeyPairEntity", b => + { + b.Property("Owner") + .HasColumnType("uuid"); + + b.Property("Created") + .HasColumnType("timestamp with time zone"); + + b.Property("Nonce") + .IsRequired() + .HasColumnType("bytea"); + + b.Property("PrivateKey") + .IsRequired() + .HasColumnType("bytea"); + + b.Property("PublicKey") + .IsRequired() + .HasColumnType("bytea"); + + b.HasKey("Owner"); + + b.ToTable("UserKeyPair", "crypter"); + }); + + modelBuilder.Entity("Crypter.DataAccess.Entities.UserMasterKeyEntity", b => + { + b.Property("Owner") + .HasColumnType("uuid"); + + b.Property("Created") + .HasColumnType("timestamp with time zone"); + + b.Property("EncryptedKey") + .IsRequired() + .HasColumnType("bytea"); + + b.Property("Nonce") + .IsRequired() + .HasColumnType("bytea"); + + b.Property("RecoveryProof") + .IsRequired() + .HasColumnType("bytea"); + + b.Property("Updated") + .HasColumnType("timestamp with time zone"); + + b.HasKey("Owner"); + + b.ToTable("UserMasterKey", "crypter"); + }); + + modelBuilder.Entity("Crypter.DataAccess.Entities.UserMessageTransferEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Created") + .HasColumnType("timestamp with time zone"); + + b.Property("Expiration") + .HasColumnType("timestamp with time zone"); + + b.Property("KeyExchangeNonce") + .IsRequired() + .HasColumnType("bytea"); + + b.Property("Proof") + .IsRequired() + .HasColumnType("bytea"); + + b.Property("PublicKey") + .HasColumnType("bytea"); + + b.Property("RecipientId") + .HasColumnType("uuid") + .HasColumnName("Recipient"); + + b.Property("SenderId") + .HasColumnType("uuid") + .HasColumnName("Sender"); + + b.Property("Size") + .HasColumnType("bigint"); + + b.Property("Subject") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("RecipientId"); + + b.HasIndex("SenderId"); + + b.ToTable("UserMessageTransfer", "crypter"); + }); + + modelBuilder.Entity("Crypter.DataAccess.Entities.UserNotificationSettingEntity", b => + { + b.Property("Owner") + .HasColumnType("uuid"); + + b.Property("EmailNotifications") + .HasColumnType("boolean"); + + b.Property("EnableTransferNotifications") + .HasColumnType("boolean"); + + b.HasKey("Owner"); + + b.ToTable("UserNotificationSetting", "crypter"); + }); + + modelBuilder.Entity("Crypter.DataAccess.Entities.UserPrivacySettingEntity", b => + { + b.Property("Owner") + .HasColumnType("uuid"); + + b.Property("AllowKeyExchangeRequests") + .HasColumnType("boolean"); + + b.Property("ReceiveFiles") + .HasColumnType("integer"); + + b.Property("ReceiveMessages") + .HasColumnType("integer"); + + b.Property("Visibility") + .HasColumnType("integer"); + + b.HasKey("Owner"); + + b.ToTable("UserPrivacySetting", "crypter"); + }); + + modelBuilder.Entity("Crypter.DataAccess.Entities.UserProfileEntity", b => + { + b.Property("Owner") + .HasColumnType("uuid"); + + b.Property("About") + .IsRequired() + .HasColumnType("text"); + + b.Property("Alias") + .IsRequired() + .HasColumnType("text"); + + b.Property("Image") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Owner"); + + b.ToTable("UserProfile", "crypter"); + }); + + modelBuilder.Entity("Crypter.DataAccess.Entities.UserRecoveryEntity", b => + { + b.Property("Owner") + .HasColumnType("uuid"); + + b.Property("Code") + .HasColumnType("uuid"); + + b.Property("Created") + .HasColumnType("timestamp with time zone"); + + b.Property("VerificationKey") + .IsRequired() + .HasColumnType("bytea"); + + b.HasKey("Owner"); + + b.HasIndex("Code") + .IsUnique(); + + b.ToTable("UserRecovery", "crypter"); + }); + + modelBuilder.Entity("Crypter.DataAccess.Entities.UserTokenEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Created") + .HasColumnType("timestamp with time zone"); + + b.Property("Description") + .IsRequired() + .HasColumnType("text"); + + b.Property("Expiration") + .HasColumnType("timestamp with time zone"); + + b.Property("Owner") + .HasColumnType("uuid"); + + b.Property("Type") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("Owner"); + + b.ToTable("UserToken", "crypter"); + }); + + modelBuilder.Entity("Crypter.DataAccess.Entities.UserConsentEntity", b => + { + b.HasOne("Crypter.DataAccess.Entities.UserEntity", "User") + .WithMany("Consents") + .HasForeignKey("Owner") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Crypter.DataAccess.Entities.UserContactEntity", b => + { + b.HasOne("Crypter.DataAccess.Entities.UserEntity", "Contact") + .WithMany("Contactors") + .HasForeignKey("ContactId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Crypter.DataAccess.Entities.UserEntity", "Owner") + .WithMany("Contacts") + .HasForeignKey("OwnerId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Contact"); + + b.Navigation("Owner"); + }); + + modelBuilder.Entity("Crypter.DataAccess.Entities.UserEmailChangeEntity", b => + { + b.HasOne("Crypter.DataAccess.Entities.UserEntity", "User") + .WithOne("EmailChange") + .HasForeignKey("Crypter.DataAccess.Entities.UserEmailChangeEntity", "Owner") + .OnDelete(DeleteBehavior.Cascade); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Crypter.DataAccess.Entities.UserFailedLoginEntity", b => + { + b.HasOne("Crypter.DataAccess.Entities.UserEntity", "User") + .WithMany("FailedLoginAttempts") + .HasForeignKey("Owner") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Crypter.DataAccess.Entities.UserFileTransferEntity", b => + { + b.HasOne("Crypter.DataAccess.Entities.UserEntity", "Recipient") + .WithMany("ReceivedFileTransfers") + .HasForeignKey("RecipientId"); + + b.HasOne("Crypter.DataAccess.Entities.UserEntity", "Sender") + .WithMany("SentFileTransfers") + .HasForeignKey("SenderId"); + + b.Navigation("Recipient"); + + b.Navigation("Sender"); + }); + + modelBuilder.Entity("Crypter.DataAccess.Entities.UserKeyPairEntity", b => + { + b.HasOne("Crypter.DataAccess.Entities.UserEntity", "User") + .WithOne("KeyPair") + .HasForeignKey("Crypter.DataAccess.Entities.UserKeyPairEntity", "Owner") + .OnDelete(DeleteBehavior.Cascade); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Crypter.DataAccess.Entities.UserMasterKeyEntity", b => + { + b.HasOne("Crypter.DataAccess.Entities.UserEntity", "User") + .WithOne("MasterKey") + .HasForeignKey("Crypter.DataAccess.Entities.UserMasterKeyEntity", "Owner") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Crypter.DataAccess.Entities.UserMessageTransferEntity", b => + { + b.HasOne("Crypter.DataAccess.Entities.UserEntity", "Recipient") + .WithMany("ReceivedMessageTransfers") + .HasForeignKey("RecipientId"); + + b.HasOne("Crypter.DataAccess.Entities.UserEntity", "Sender") + .WithMany("SentMessageTransfers") + .HasForeignKey("SenderId"); + + b.Navigation("Recipient"); + + b.Navigation("Sender"); + }); + + modelBuilder.Entity("Crypter.DataAccess.Entities.UserNotificationSettingEntity", b => + { + b.HasOne("Crypter.DataAccess.Entities.UserEntity", "User") + .WithOne("NotificationSetting") + .HasForeignKey("Crypter.DataAccess.Entities.UserNotificationSettingEntity", "Owner") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Crypter.DataAccess.Entities.UserPrivacySettingEntity", b => + { + b.HasOne("Crypter.DataAccess.Entities.UserEntity", "User") + .WithOne("PrivacySetting") + .HasForeignKey("Crypter.DataAccess.Entities.UserPrivacySettingEntity", "Owner") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Crypter.DataAccess.Entities.UserProfileEntity", b => + { + b.HasOne("Crypter.DataAccess.Entities.UserEntity", "User") + .WithOne("Profile") + .HasForeignKey("Crypter.DataAccess.Entities.UserProfileEntity", "Owner") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Crypter.DataAccess.Entities.UserRecoveryEntity", b => + { + b.HasOne("Crypter.DataAccess.Entities.UserEntity", "User") + .WithOne("Recovery") + .HasForeignKey("Crypter.DataAccess.Entities.UserRecoveryEntity", "Owner") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Crypter.DataAccess.Entities.UserTokenEntity", b => + { + b.HasOne("Crypter.DataAccess.Entities.UserEntity", "User") + .WithMany("Tokens") + .HasForeignKey("Owner") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Crypter.DataAccess.Entities.UserEntity", b => + { + b.Navigation("Consents"); + + b.Navigation("Contactors"); + + b.Navigation("Contacts"); + + b.Navigation("EmailChange"); + + b.Navigation("FailedLoginAttempts"); + + b.Navigation("KeyPair"); + + b.Navigation("MasterKey"); + + b.Navigation("NotificationSetting"); + + b.Navigation("PrivacySetting"); + + b.Navigation("Profile"); + + b.Navigation("ReceivedFileTransfers"); + + b.Navigation("ReceivedMessageTransfers"); + + b.Navigation("Recovery"); + + b.Navigation("SentFileTransfers"); + + b.Navigation("SentMessageTransfers"); + + b.Navigation("Tokens"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/Crypter.DataAccess/Migrations/20250424020928_AddMaximumMessageLengthColumn.cs b/Crypter.DataAccess/Migrations/20250424020928_AddMaximumMessageLengthColumn.cs new file mode 100644 index 00000000..1c35068e --- /dev/null +++ b/Crypter.DataAccess/Migrations/20250424020928_AddMaximumMessageLengthColumn.cs @@ -0,0 +1,39 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace Crypter.DataAccess.Migrations +{ + /// + public partial class AddMaximumMessageLengthColumn : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AddColumn( + name: "MaximumMessageLength", + schema: "crypter", + table: "TransferTier", + type: "integer", + nullable: false, + defaultValue: 0); + + migrationBuilder.UpdateData( + schema: "crypter", + table: "TransferTier", + keyColumn: "Id", + keyValues: [1, 2, 3], + column: "MaximumMessageLength", + values: [1024, 4096, 4096]); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropColumn( + name: "MaximumMessageLength", + schema: "crypter", + table: "TransferTier"); + } + } +} diff --git a/Crypter.DataAccess/Migrations/DataContextModelSnapshot.cs b/Crypter.DataAccess/Migrations/DataContextModelSnapshot.cs index 81c5c77d..fd5be462 100644 --- a/Crypter.DataAccess/Migrations/DataContextModelSnapshot.cs +++ b/Crypter.DataAccess/Migrations/DataContextModelSnapshot.cs @@ -160,6 +160,9 @@ protected override void BuildModel(ModelBuilder modelBuilder) .HasMaxLength(256) .HasColumnType("character varying(256)"); + b.Property("MaximumMessageLength") + .HasColumnType("integer"); + b.Property("MaximumUploadSize") .HasColumnType("bigint"); diff --git a/Crypter.Web/Shared/Transfer/UploadFileTransfer.razor.cs b/Crypter.Web/Shared/Transfer/UploadFileTransfer.razor.cs index 04e9d8a5..dabb1998 100644 --- a/Crypter.Web/Shared/Transfer/UploadFileTransfer.razor.cs +++ b/Crypter.Web/Shared/Transfer/UploadFileTransfer.razor.cs @@ -28,6 +28,7 @@ using System.IO; using System.Runtime.Versioning; using System.Threading.Tasks; +using ByteSizeLib; using Crypter.Common.Client.Enums; using Crypter.Common.Client.Transfer.Handlers; using Crypter.Common.Client.Transfer.Models; @@ -43,8 +44,8 @@ public partial class UploadFileTransfer : IDisposable { private IBrowserFile? _selectedFile; - private long _maximumBufferSize = 0; - private long _maximumUploadSize = 0; + private long _absoluteMaximumBufferSize = 0; + private long _currentMaximumUploadSize = 0; private string _dropClass = string.Empty; private const string DropzoneDrag = "dropzone-drag"; @@ -52,15 +53,14 @@ public partial class UploadFileTransfer : IDisposable protected override void OnInitialized() { - _maximumBufferSize = ClientTransferSettings.MaximumUploadBufferSizeMB * Convert.ToInt64(Math.Pow(10, 6)); + _absoluteMaximumBufferSize = ClientTransferSettings.MaximumUploadBufferSizeMB * Convert.ToInt64(Math.Pow(10, 6)); } - protected override async Task OnInitializedAsync() + protected override async Task OnParametersSetAsync() { - _maximumUploadSize = await UserTransferSettingsService.GetTransferSettingsAsync() - .MatchAsync( - () => 0, - x => x.MaximumUploadSize); + _currentMaximumUploadSize = await UserTransferSettingsService.GetCurrentMaximumUploadSizeAsync(); + StateHasChanged(); + await base.OnParametersSetAsync(); } private void HandleDragEnter() @@ -88,16 +88,20 @@ private async Task HandleFileInputChangeAsync(InputFileChangeEventArgs e) { TransmissionType = TransferTransmissionType.Stream; } + else + { + TransmissionType = TransferTransmissionType.Buffer; + } // ReSharper disable once SwitchStatementHandlesSomeKnownEnumValuesWithDefault switch (TransmissionType) { - case TransferTransmissionType.Buffer when file.Size > _maximumBufferSize: - ErrorMessage = $"The max file size is {ClientTransferSettings.MaximumUploadBufferSizeMB} MB. Login to upload larger files."; + case TransferTransmissionType.Buffer when file.Size > _absoluteMaximumBufferSize: + ErrorMessage = $"The max file size is {ByteSize.FromBytes(_absoluteMaximumBufferSize)}. Login to upload larger files."; break; case TransferTransmissionType.Stream - or TransferTransmissionType.Multipart when file.Size > _maximumUploadSize: - ErrorMessage = $"The max file size is {_maximumUploadSize / Math.Pow(10, 6)} MB."; + or TransferTransmissionType.Multipart when file.Size > _currentMaximumUploadSize: + ErrorMessage = $"The max file size is {ByteSize.FromBytes(_currentMaximumUploadSize)}."; break; default: _selectedFile = file; diff --git a/Crypter.Web/Shared/Transfer/UploadMessageTransfer.razor b/Crypter.Web/Shared/Transfer/UploadMessageTransfer.razor index 9afebf08..9c53214d 100644 --- a/Crypter.Web/Shared/Transfer/UploadMessageTransfer.razor +++ b/Crypter.Web/Shared/Transfer/UploadMessageTransfer.razor @@ -30,13 +30,13 @@
- +
- +
diff --git a/Crypter.Web/Shared/Transfer/UploadMessageTransfer.razor.cs b/Crypter.Web/Shared/Transfer/UploadMessageTransfer.razor.cs index 8b944f61..c19133af 100644 --- a/Crypter.Web/Shared/Transfer/UploadMessageTransfer.razor.cs +++ b/Crypter.Web/Shared/Transfer/UploadMessageTransfer.razor.cs @@ -25,7 +25,6 @@ */ using System; -using System.Reflection.Metadata; using System.Threading.Tasks; using Crypter.Common.Client.Transfer.Handlers; using Crypter.Common.Client.Transfer.Models; @@ -36,17 +35,26 @@ namespace Crypter.Web.Shared.Transfer; public partial class UploadMessageTransfer : IDisposable { - protected string MessageSubject = string.Empty; - protected string MessageBody = string.Empty; - protected const int _maxMessageLength = 1024; + private string _messageSubject = string.Empty; + private string _messageBody = string.Empty; + private int _maxMessageLength = 0; - protected async Task OnEncryptClicked() + protected override async Task OnParametersSetAsync() + { + long maximumUploadSize = await UserTransferSettingsService.GetCurrentMaximumUploadSizeAsync(); + int maximumMessageLength = await UserTransferSettingsService.GetAbsoluteMaximumMessageLengthAsync(); + _maxMessageLength = Math.Min((int)Math.Min(maximumUploadSize, int.MaxValue), maximumMessageLength); + StateHasChanged(); + await base.OnParametersSetAsync(); + } + + private async Task OnEncryptClicked() { EncryptionInProgress = true; ErrorMessage = string.Empty; await SetProgressMessageAsync("Encrypting message"); - UploadMessageHandler messageUploader = TransferHandlerFactory.CreateUploadMessageHandler(MessageSubject, MessageBody, ExpirationHours); + UploadMessageHandler messageUploader = TransferHandlerFactory.CreateUploadMessageHandler(_messageSubject, _messageBody, ExpirationHours); SetHandlerUserInfo(messageUploader); Either uploadResponse = await messageUploader.UploadAsync(); @@ -54,7 +62,7 @@ protected async Task OnEncryptClicked() Dispose(); } - protected async Task SetProgressMessageAsync(string message) + private async Task SetProgressMessageAsync(string message) { UploadStatusMessage = message; StateHasChanged(); @@ -63,8 +71,8 @@ protected async Task SetProgressMessageAsync(string message) public void Dispose() { - MessageSubject = string.Empty; - MessageBody = string.Empty; + _messageSubject = string.Empty; + _messageBody = string.Empty; EncryptionInProgress = false; GC.SuppressFinalize(this); }