Feat/50/manage interesting facts - #75
Conversation
DrFaust555
left a comment
There was a problem hiding this comment.
-
ImageDescription is stored on ImageDetails, which belongs to the Image, not to the Fact. An Image can be referenced by other facts, arts and streetcodes. Consequences: two facts sharing one image cannot have different descriptions, and writing a description through a fact rewrites the alt text everywhere that image is used. Either put the description on the Fact, or state in the description that it is image scoped and rename the field to ImageAlt so callers are not misled.
-
UpdateFactHandler sets image.ImageDetails.Alt = null whenever ImageDescription is empty or absent from the request body. Combined with item 1, a fact update that simply omits the field wipes the alt text that another entity depends on. Decide the semantics: either only write Alt when the caller sent a value, or document that omitting it clears it.
-
GetFactByStreetcodeIdHandler no longer returns a failure when a streetcode has no facts. It now returns 200 with an empty array. The old behaviour was 400 with "Cannot find any fact by the streetcode id". This is a breaking change for existing clients and it is not in the description. Add it, or keep the failure.
Also:
- CreateFactHandler computes DisplayOrder as Max(DisplayOrder) + 1 with no unique constraint on (StreetcodeId, DisplayOrder). Two concurrent creates produce two facts at the same position. Either add the constraint and retry on conflict, or note the limitation.
On your open question: image extension validation belongs to the image upload endpoint, not here. Raise it as a separate task against ImageController/Create.
DrFaust555
left a comment
There was a problem hiding this comment.
The description says "Preserved the existing failure response when no facts
exist". That is not accurate. On dev the if (fact is null) branch in
GetFactByStreetcodeIdHandler is unreachable: RepositoryBase.GetAllAsync
returns ToListAsync(), which is an empty list, never null. dev therefore
answers 200 with an empty array. The PR changes that to 400. Describe it as a
behaviour change and confirm it with the frontend.
-
Image extension validation is only mentioned in the description as a
follow-up. Open the actual issue against ImageController/Create so it is
tracked. -
ReorderFactsHandler: when a streetcode has no facts and OrderedFactIds is
empty, SetEquals passes, UpdateRange gets an empty collection,
SaveChangesAsync() returns 0 and the handler reports "Failed to reorder facts
for streetcode with id: N". A no-op reorder should succeed. -
FactUpdateCreateDto is validated with DataAnnotations, while PR #76 moves
request validation into a FluentValidation MediatR pipeline. After both merge,
fact create/update will return a different 400 payload than the rest of the
API, and CreateFactCommand, UpdateFactCommand and ReorderFactsCommand will
have no validators at all - ReorderFactsCommand does not even check
StreetcodeId. Agree the approach with #76 before merge. -
The guard in 20260818144116_LimitFactTitleLength uses LEN([Title]), which
ignores trailing spaces. A title padded past 68 characters passes the guard
and then fails inside ALTER COLUMN. Use DATALENGTH([Title]) / 2. -
Fix the StyleCop violations Sonar reports in the new test files: SA1101,
SA1200, SA1633, SA1309, SA1000
Loki22978964
left a comment
There was a problem hiding this comment.
Good, safe migration of LimitFactTitleLength (with a THROW exception when attempting to truncate longer data), logically consistent Create/Delete/Reorder methods (correctly update DisplayOrder), strong test coverage (success/failure/edge cases), and removal of unnecessary nullable noise in DTOs/entities.
Fixes prior to the merge:
-
Logger bug in GetFactByStreetcodeIdHandler (critical)
The constructor renamed the field to _loggerService, but inside the method body (if (fact is null)), the old name _logger—which no longer exists—is still being called. SonarCloud flagged this as a failure—it either fails to compile or leaves a dead field. The renaming needs to be completed. -
Duplicate ImageAlt logic (important)
The “trim + create/update ImageDetails” block is repeated almost identically in both CreateFactHandler and UpdateFactHandler. It should be moved to a shared method or service to avoid having to edit the logic in two places during future changes. -
Sonar-style warnings (minor)
A space before the closing parenthesis in records (CreateFactCommand, DeleteFactCommand, etc.), unchained Include+ThenInclude, and several methods in tests that can be made static. Easy to fix in a single pass. -
Potential race condition when calculating DisplayOrder (worth considering)
existingFacts.Max(f => f.DisplayOrder) + 1without a unique constraint or locking—with parallel requests to create a fact for the same streetcode, a duplicate DisplayOrder is possible. This is unlikely for the admin panel, but it can be logged as tech debt or a unique index can be added. -
Authorization of mutating endpoints (check)
In FactController, attributes such as [Authorize] for Create/Update/Delete/Reorder are not visible. It’s worth checking whether this was overlooked, especially if the project has a convention to secure admin CRUD operations. -
Validation of ImageAlt when null (check)
Ensure that MustNotExceedLength(200, ...) for ImageAlt correctly allows null—so that a fact can be updated without changing the alt (the handler tests confirm this; the main thing is that the validator doesn’t break earlier in the pipeline).
|
Loki22978964
left a comment
There was a problem hiding this comment.
Everything looks fine for me


dev
JIRA
Code reviewers
Second Level Review
Summary of issue
The backend did not fully support administration of the Interesting Facts block. Administrators needed API operations for creating, updating, deleting, retrieving, and reordering facts while preserving validation rules and a stable display order.
The API also needed to enforce the required text limits:
Summary of change
POST,PUT,DELETE, and reorder endpoints toFactController.ImageIdandStreetcodeId.ImageDescriptiontoImageAltbecause the value is stored onImageDetailsand belongs to the Image rather than to a specific Fact.ImageAltaffects every entity that references the same Image.ImageAlt:nullvalue preserves the existing Alt;ImageDetailswhenImageAltis supplied.DisplayOrderfor each fact.DisplayOrder.LimitFactTitleLengthEF Core migration.Testing approach
dotnet ef migrations has-pending-model-changesand confirmed that no model changes are pending.ImageIdandStreetcodeId.DisplayOrder;ImageAltpreserves the existing image Alt;ImageAltclears it;Scope note
ImageAltis stored onImageDetailsand is therefore image-scoped. If several facts, arts, or Streetcodes reference the same Image, changing its Alt through one Fact changes it for every other reference to that Image.Authentication and authorization are not implemented in this PR because the project does not currently register an authentication scheme. Authorization of admin endpoints is handled by separate authentication and access-control tasks.
The modal window, dynamic character counters, action icons, and drag-and-drop interface are frontend responsibilities. This PR provides the backend CRUD and reorder operations required by those features.
Known limitation
DisplayOrderfor a newly created Fact is calculated as the current maximum order plus one. There is currently no unique constraint on(StreetcodeId, DisplayOrder), so two concurrent create requests for the same Streetcode may receive the same position. Database-level enforcement and conflict retry should be handled in a separate follow-up task.Follow-up
Image extension validation for
.jpeg,.jpg,.png, and.webpbelongs to the sharedImageController/Createupload flow and will be handled in a separate task.Closes #50
CHECK LIST