From 90dfe9c13fef198376d53aa9cf07578537651f8f Mon Sep 17 00:00:00 2001 From: Paul Schneider Date: Sat, 4 Jul 2026 16:06:02 +0100 Subject: [PATCH] drop the deigner.cs --- .../Resources/Yavsc.ChatHubLabels.Designer.cs | 82 ------------------- .../Controllers/Business/BillingController.cs | 60 ++++++++++---- src/Yavsc.Server/Hubs/HubInputValidator.cs | 8 +- .../Models/ApplicationDbContext.cs | 19 +++-- 4 files changed, 60 insertions(+), 109 deletions(-) delete mode 100644 src/Yavsc.Abstract/Resources/Yavsc.ChatHubLabels.Designer.cs diff --git a/src/Yavsc.Abstract/Resources/Yavsc.ChatHubLabels.Designer.cs b/src/Yavsc.Abstract/Resources/Yavsc.ChatHubLabels.Designer.cs deleted file mode 100644 index bdfc3776..00000000 --- a/src/Yavsc.Abstract/Resources/Yavsc.ChatHubLabels.Designer.cs +++ /dev/null @@ -1,82 +0,0 @@ -// ------------------------------------------------------------------------------ -// -// This code was generated by a tool. -// Mono Runtime Version: 4.0.30319.42000 -// -// Changes to this file may cause incorrect behavior and will be lost if -// the code is regenerated. -// -// ------------------------------------------------------------------------------ - -namespace Yavsc { - using System; - using System.Reflection; - - - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")] - [System.Diagnostics.DebuggerNonUserCodeAttribute()] - [System.Runtime.CompilerServices.CompilerGeneratedAttribute()] - public partial class ChatHubLabels { - - private static System.Resources.ResourceManager resourceMan; - - private static System.Globalization.CultureInfo resourceCulture; - - [System.ComponentModel.EditorBrowsableAttribute(System.ComponentModel.EditorBrowsableState.Advanced)] - public static System.Resources.ResourceManager ResourceManager { - get { - if (object.Equals(null, resourceMan)) { - System.Resources.ResourceManager temp = new System.Resources.ResourceManager(("Yavsc.Abstract.Resources." + "Yavsc.ChatHub"), typeof(ChatHubLabels).GetTypeInfo().Assembly); - resourceMan = temp; - } - return resourceMan; - } - } - - [System.ComponentModel.EditorBrowsableAttribute(System.ComponentModel.EditorBrowsableState.Advanced)] - public static System.Globalization.CultureInfo Culture { - get { - return resourceCulture; - } - set { - resourceCulture = value; - } - } - - public static string Authenticated_chat_user { - get { - return ResourceManager.GetString("Authenticated chat user", resourceCulture); - } - } - - public static string LabnoJoinNoSend { - get { - return ResourceManager.GetString("LabnoJoinNoSend", resourceCulture); - } - } - - public static string InvalidRoomName { - get { - return ResourceManager.GetString("InvalidRoomName", resourceCulture); - } - } - - public static string InvalidUserName { - get { - return ResourceManager.GetString("InvalidUserName", resourceCulture); - } - } - - public static string InvalidMessage { - get { - return ResourceManager.GetString("InvalidMessage", resourceCulture); - } - } - - public static string InvalidReason { - get { - return ResourceManager.GetString("InvalidReason", resourceCulture); - } - } - } -} diff --git a/src/Yavsc.Api/Controllers/Business/BillingController.cs b/src/Yavsc.Api/Controllers/Business/BillingController.cs index a7047049..b26f3bb5 100644 --- a/src/Yavsc.Api/Controllers/Business/BillingController.cs +++ b/src/Yavsc.Api/Controllers/Business/BillingController.cs @@ -264,21 +264,53 @@ namespace Yavsc.ApiControllers return BadRequest(new { Error = "file write failed", Detail = ex.Message }); } - // Now persist the database row. The int[] is round- - // tripped via Npgsql's native int[] mapping; the - // migration (separate commit) introduces the column - // and the index. - var signature = new Signature + // Find-or-add: the (EstimateId, Type) pair is + // unique, so a second POST for the same side of the + // estimate replaces the previous signature. EF + // translates this into a single UPDATE when the + // row exists and an INSERT otherwise; the unique + // index in ApplicationDbContext is the + // database-level guarantee that the contract + // holds if two requests race. + var signature = await dbContext.Signatures + .FirstOrDefaultAsync(s => s.EstimateId == id && s.Type == type, token); + + if (signature is null) { - EstimateId = id, - SignerId = userId, - Type = type, - CoordinateMax = payload.CoordinateMax, - Strokes = payload.Strokes, - CapturedAtUtc = payload.CapturedAtUtc, - FilePath = Path.Combine(fi.DestDir, fi.FileName), - }; - dbContext.Signatures.Add(signature); + signature = new Signature + { + EstimateId = id, + SignerId = userId, + Type = type, + }; + dbContext.Signatures.Add(signature); + } + else + { + // Roll the signer's quota back by the size of + // the file we're about to orphan: the old + // FilePath is no longer referenced once we + // overwrite FilePath below. + try + { + var orphan = new FileInfo(signature.FilePath); + if (orphan.Exists) + { + var signerForOrphan = await dbContext.Users + .FirstOrDefaultAsync(u => u.Id == userId, token); + if (signerForOrphan is not null) + signerForOrphan.DiskUsage = + Math.Max(0, signerForOrphan.DiskUsage - orphan.Length); + } + } + catch { /* best effort — the file is being replaced anyway */ } + } + + signature.SignerId = userId; + signature.CoordinateMax = payload.CoordinateMax; + signature.Strokes = payload.Strokes; + signature.CapturedAtUtc = payload.CapturedAtUtc; + signature.FilePath = Path.Combine(fi.DestDir, fi.FileName); // Bump the signer's quota. The Signature row's // SignerId is the IdentityUser.Id (a string), so we diff --git a/src/Yavsc.Server/Hubs/HubInputValidator.cs b/src/Yavsc.Server/Hubs/HubInputValidator.cs index 36e5dce2..a6acac5a 100644 --- a/src/Yavsc.Server/Hubs/HubInputValidator.cs +++ b/src/Yavsc.Server/Hubs/HubInputValidator.cs @@ -35,7 +35,7 @@ namespace Yavsc.Server.Hubs { bool valid = ValidateStringLength(roomName,1,25); if (valid) valid = IsLetterOrDigit(roomName); - if (!valid) NotifyUser(NotificationTypes.Error, "roomName", _locator.GetString(ChatHubLabels.InvalidRoomName)); + if (!valid) NotifyUser(NotificationTypes.Error, "roomName", _locator.GetString("InvalidRoomName")); return valid; } public bool ValidateUserName (string userName) @@ -52,14 +52,14 @@ namespace Yavsc.Server.Hubs NotifyUser( NotificationTypes.Error, "userName", - _locator.GetString(ChatHubLabels.InvalidUserName)); + _locator.GetString("InvalidUserName)")); return valid; } public bool ValidateMessage (string message) { if (!ValidateStringLength(message, 1, 10240)) { - NotifyUser(NotificationTypes.Error, "message", _locator.GetString(ChatHubLabels.InvalidMessage)); + NotifyUser(NotificationTypes.Error, "message", _locator.GetString("InvalidMessage")); return false; } return true; @@ -68,7 +68,7 @@ namespace Yavsc.Server.Hubs { if (!ValidateStringLength(reason, 1,240)) { - NotifyUser(NotificationTypes.Error, "reason", ChatHubLabels.InvalidReason); + NotifyUser(NotificationTypes.Error, "reason", "InvalidReason"); return false; } return true; diff --git a/src/Yavsc.Server/Models/ApplicationDbContext.cs b/src/Yavsc.Server/Models/ApplicationDbContext.cs index b8ade1c2..cbdb6895 100644 --- a/src/Yavsc.Server/Models/ApplicationDbContext.cs +++ b/src/Yavsc.Server/Models/ApplicationDbContext.cs @@ -69,16 +69,17 @@ namespace Yavsc.Models builder.Entity().Property(x => x.DeclarationDate).HasDefaultValueSql(NOW_SQL); builder.Entity().HasKey(x => new { x.PostId, x.TagId }); - // Signature: composite index (EstimateId, Type, - // CapturedAtUtc DESC) to support the controller's - // "most recent signature per type" read pattern - // without an extra ORDER BY cost. The default - // EF-generated FK index on EstimateId alone is - // replaced by the composite to avoid duplicate - // indexes. + // Signature: unique constraint on (EstimateId, Type) + // — the most recent version replaces the previous + // one, so a partial unique index (not a regular one) + // is the right shape. The controller does a + // Find-or-Add so EF translates an upsert into a + // single UPDATE when the row already exists; the + // unique index is the database-level guarantee that + // the contract holds. builder.Entity() - .HasIndex(s => new { s.EstimateId, s.Type, s.CapturedAtUtc }) - .IsDescending(false, false, true); + .HasIndex(s => new { s.EstimateId, s.Type }) + .IsUnique(); builder.Entity() .HasOne(s => s.Estimate) .WithMany(e => e.Signatures)