Skip to content

Commit ca3c14a

Browse files
authored
Show validation for server claim field validation (#2826)
* ClaimServiceImpl to primary ctor * Add some logging for claims * Show validation for server claim field validation
1 parent cdb64a8 commit ca3c14a

File tree

8 files changed

+63
-35
lines changed

8 files changed

+63
-35
lines changed

src/JoinRpg.Domain.Test/FieldSaveHelperTest.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -247,7 +247,7 @@ public void TryToSkipMandatoryField()
247247

248248
var claim = mock.CreateApprovedClaim(mock.Character, mock.Player);
249249

250-
var exception = Should.Throw<FieldRequiredException>(() =>
250+
var exception = Should.Throw<CharacterFieldRequiredException>(() =>
251251
InitFieldSaveHelper().SaveCharacterFields(
252252
mock.Player.UserId,
253253
claim,

src/JoinRpg.Domain/CharacterFields/FieldSaveHelper.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -106,7 +106,7 @@ private static void AssignValues(IReadOnlyDictionary<int, string?> newFieldValue
106106

107107
if (normalizedValue is null && field.Field.MandatoryStatus == MandatoryStatus.Required)
108108
{
109-
throw new FieldRequiredException(field.Field.Name);
109+
throw new CharacterFieldRequiredException(field.Field.Name, field.Field.Id);
110110
}
111111

112112
_ = strategy.AssignFieldValue(field, normalizedValue);

src/JoinRpg.Domain/Exceptions.cs

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
using System.Linq.Expressions;
22
using JoinRpg.DataModel;
33
using JoinRpg.Helpers;
4+
using JoinRpg.PrimitiveTypes;
45

56
namespace JoinRpg.Domain;
67

@@ -233,11 +234,14 @@ public NoAccessToProjectException(IProjectEntity entity, int? userId)
233234
: base(entity, $"No access to entity of {entity.Project.ProjectName} for user {userId}") => UserId = userId;
234235
}
235236

236-
public class FieldRequiredException : JoinRpgBaseException
237+
public class FieldRequiredException(string fieldName) : JoinRpgBaseException($"Field {fieldName} is required")
237238
{
238-
public string FieldName { get; }
239+
public string FieldName { get; } = fieldName;
240+
}
239241

240-
public FieldRequiredException(string fieldName) : base($"{fieldName}") => FieldName = fieldName;
242+
public class CharacterFieldRequiredException(string fieldName, ProjectFieldIdentification fieldId) : FieldRequiredException(fieldName)
243+
{
244+
public ProjectFieldIdentification FieldId { get; } = fieldId;
241245
}
242246

243247
public class ValueAlreadySetException : JoinRpgBaseException

src/JoinRpg.Portal/Controllers/ClaimController.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -113,7 +113,7 @@ await claimService.AddClaimFromUser(viewModel.ProjectId, viewModel.CharacterGrou
113113
catch (Exception exception)
114114
{
115115
ModelState.AddException(exception);
116-
var source = await ProjectRepository.GetClaimSource(viewModel.ProjectId, viewModel.CharacterGroupId, viewModel.CharacterId).ConfigureAwait(false);
116+
var source = await ProjectRepository.GetClaimSource(viewModel.ProjectId, viewModel.CharacterGroupId, viewModel.CharacterId);
117117
var projectInfo = await projectMetadataRepository.GetProjectMetadata(new ProjectIdentification(viewModel.ProjectId));
118118
viewModel.Fill(source, CurrentUserId, projectInfo, Request.GetDynamicValuesFromPost(FieldValueViewModel.HtmlIdPrefix));
119119
return base.View(viewModel);

src/JoinRpg.Portal/Infrastructure/ModelStateExtensions.cs

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ public static void AddException(this ModelStateDictionary dict, Exception except
1515
case DbEntityValidationException validation:
1616
var dbValidationErrors = validation.EntityValidationErrors
1717
.SelectMany(eve => eve.ValidationErrors).ToList();
18-
if (dbValidationErrors.Any())
18+
if (dbValidationErrors.Count != 0)
1919
{
2020
foreach (var error in dbValidationErrors)
2121
{
@@ -30,10 +30,15 @@ public static void AddException(this ModelStateDictionary dict, Exception except
3030

3131
dict.AddModelError("", exception.ToString());
3232
return;
33-
33+
case CharacterFieldRequiredException required:
34+
var errorMessage = GetErrorMessage(required);
35+
dict.AddModelError("", errorMessage);
36+
dict.AddModelError(Web.Models.FieldValueViewModel.HtmlIdPrefix + required.FieldId.ProjectFieldId, errorMessage);
37+
return;
3438
case FieldRequiredException required:
35-
dict.AddModelError("", required.FieldName + " is required");
36-
dict.AddModelError(required.FieldName, " required");
39+
var errorMessage1 = GetErrorMessage(required);
40+
dict.AddModelError("", errorMessage1);
41+
dict.AddModelError(required.FieldName, errorMessage1);
3742
return;
3843
case ClaimWrongStatusException _:
3944
case ProjectDeactivatedException _:
@@ -54,4 +59,6 @@ public static void AddException(this ModelStateDictionary dict, Exception except
5459
break;
5560
}
5661
}
62+
63+
private static string GetErrorMessage(FieldRequiredException required) => $"{required.FieldName} — обязательное поле";
5764
}

src/JoinRpg.Portal/Views/Claim/Add.cshtml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -134,6 +134,8 @@
134134
{
135135
ViewBag.HideCharacterClaimFieldsIcon = true;
136136

137+
@Html.ValidationSummary(true, "", new { @class = "text-danger" })
138+
137139
@await Html.PartialAsync("_EditFieldsPartial", Model.Fields)
138140

139141
if (Model.Fields.Fields.Any(f => f.HasPrice))

src/JoinRpg.Portal/Views/Shared/EditorTemplates/FieldValueViewModel.cshtml

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -194,6 +194,22 @@
194194
default:
195195
throw new InvalidEnumArgumentException("Unknown field type name");
196196
}
197+
@{
198+
var tryGetModelStateResult = ViewData.ModelState.TryGetValue(Model.FieldClientId, out var entry);
199+
var modelErrors = tryGetModelStateResult ? entry?.Errors : null;
200+
Microsoft.AspNetCore.Mvc.ModelBinding.ModelError modelError = null;
201+
if (modelErrors != null && modelErrors.Count != 0)
202+
{
203+
modelError = modelErrors.FirstOrDefault(m => !string.IsNullOrEmpty(m.ErrorMessage)) ?? modelErrors[0];
204+
}
205+
if (modelError is not null)
206+
{
207+
<span class="text-danger field-validation-error" data-valmsg-for="@Model.FieldClientId" data-valmsg-replace="true">
208+
<span id="@(Model.FieldClientId)-error" class="">@modelError.ErrorMessage</span>
209+
</span>
210+
}
211+
}
212+
197213
<span class="field-validation-valid text-danger" data-valmsg-for="@Model.FieldClientId" data-valmsg-replace="true"></span>
198214
@if (Model.HasMasterAccess)
199215
{
@@ -221,7 +237,6 @@
221237
</div>
222238
}
223239
}
224-
@Html.ValidationMessageFor(model => model.Value, "", new { @class = "text-danger" })
225240
</div>
226241

227242
<div class="col-md-2 price-table">

src/JoinRpg.Services.Impl/ClaimServiceImpl.cs

Lines changed: 24 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -11,10 +11,20 @@
1111
using JoinRpg.PrimitiveTypes.ProjectMetadata;
1212
using JoinRpg.Services.Interfaces;
1313
using JoinRpg.Services.Interfaces.Notification;
14+
using Microsoft.Extensions.Logging;
1415

1516
namespace JoinRpg.Services.Impl;
1617

17-
internal class ClaimServiceImpl : ClaimImplBase, IClaimService
18+
internal class ClaimServiceImpl(
19+
IUnitOfWork unitOfWork,
20+
IEmailService emailService,
21+
FieldSaveHelper fieldSaveHelper,
22+
IAccommodationInviteService accommodationInviteService,
23+
ICurrentUserAccessor currentUserAccessor,
24+
IProjectMetadataRepository projectMetadataRepository,
25+
IProblemValidator<Claim> claimValidator,
26+
ILogger<CharacterServiceImpl> logger)
27+
: ClaimImplBase(unitOfWork, emailService, currentUserAccessor, projectMetadataRepository), IClaimService
1828
{
1929

2030
public async Task SubscribeClaimToUser(int projectId, int claimId)
@@ -170,6 +180,14 @@ public async Task AddClaimFromUser(int projectId,
170180
string claimText,
171181
IReadOnlyDictionary<int, string?> fields)
172182
{
183+
if (characterId is not null)
184+
{
185+
logger.LogDebug("About to add claim to character {characterId}", characterId);
186+
}
187+
if (characterGroupId is not null)
188+
{
189+
logger.LogDebug("About to add claim to character {characterGroupId}", characterGroupId);
190+
}
173191
var source = await ProjectRepository.GetClaimSource(projectId, characterGroupId, characterId);
174192
var projectInfo = await ProjectMetadataRepository.GetProjectMetadata(new(projectId));
175193

@@ -223,14 +241,15 @@ public async Task AddClaimFromUser(int projectId,
223241

224242
if (claim.Project.Details.AutoAcceptClaims)
225243
{
226-
var userId = claim.ResponsibleMasterUserId;
227-
StartImpersonate(userId);
244+
StartImpersonate(claim.ResponsibleMasterUserId);
228245
//TODO[Localize]
229246
await ApproveByMaster(projectId,
230247
claim.ClaimId,
231248
"Ваша заявка была принята автоматически");
232249
ResetImpersonation();
233250
}
251+
252+
logger.LogInformation("Claim ({claimId}) was successfully send", claim.ClaimId);
234253
}
235254

236255
public async Task AddComment(int projectId, int claimId, int? parentCommentId, bool isVisibleToPlayer, string commentText, FinanceOperationAction financeAction)
@@ -485,7 +504,7 @@ public async Task DeclineByMaster(int projectId, int claimId, Claim.DenialStatus
485504
DeleteCharacter(claim.Character, CurrentUserId);
486505
}
487506

488-
await _accommodationInviteService.DeclineAllClaimInvites(claimId).ConfigureAwait(false);
507+
await accommodationInviteService.DeclineAllClaimInvites(claimId).ConfigureAwait(false);
489508

490509
var email =
491510
await
@@ -687,7 +706,7 @@ public async Task DeclineByPlayer(int projectId, int claimId, string commentText
687706
claim.ClaimStatus = Claim.Status.DeclinedByUser;
688707

689708

690-
await _accommodationInviteService.DeclineAllClaimInvites(claimId).ConfigureAwait(false);
709+
await accommodationInviteService.DeclineAllClaimInvites(claimId).ConfigureAwait(false);
691710

692711
var roomEmail = await CommonClaimDecline(claim);
693712

@@ -913,25 +932,6 @@ private void MarkCharacterChangedIfApproved(Claim claim)
913932
}
914933
}
915934

916-
private readonly FieldSaveHelper fieldSaveHelper;
917-
private readonly IAccommodationInviteService _accommodationInviteService;
918-
private readonly IPlotService plotService;
919-
private readonly IProblemValidator<Claim> claimValidator;
920-
921-
public ClaimServiceImpl(IUnitOfWork unitOfWork, IEmailService emailService,
922-
FieldSaveHelper fieldSaveHelper,
923-
IAccommodationInviteService accommodationInviteService,
924-
ICurrentUserAccessor currentUserAccessor,
925-
IPlotService plotService,
926-
IProjectMetadataRepository projectMetadataRepository,
927-
IProblemValidator<Claim> claimValidator) : base(unitOfWork, emailService, currentUserAccessor, projectMetadataRepository)
928-
{
929-
this.fieldSaveHelper = fieldSaveHelper;
930-
_accommodationInviteService = accommodationInviteService;
931-
this.plotService = plotService;
932-
this.claimValidator = claimValidator;
933-
}
934-
935935
private void SetDiscussed(Claim claim, bool isVisibleToPlayer)
936936
{
937937
claim.LastUpdateDateTime = Now;

0 commit comments

Comments
 (0)