Lets admins merge duplicate SoftwareCompilation entries: a new
POST /software-compilations/{id}/merge endpoint re-parents releases,
covers, junction rows (with dedup/self-loop guards), and predecessor
references onto the target before deleting the sources. Wired into
both the Blazor MudBlazor admin page and the Uno admin page, each
with a search-based target/source picker, plus full localization.
Software entries previously only modeled strict lineage relationships
(Predecessor/Successor sequels, BaseSoftware/Addons DLC). There was no
way to express a looser, symmetric "these are related" link, e.g.
Microsoft Excel and Microsoft Excel for Macintosh.
Adds a new self-referencing many-to-many SoftwareSimilarTo join entity,
following the existing SoftwareCompilationBySoftwareCompilation pattern
(composite PK, Cascade + Restrict delete behavior to satisfy MySQL's
single-cascade-path constraint on self-referencing tables). Each
unordered pair is stored once, normalized so the smaller id is always
SoftwareId, avoiding duplicate inverse rows.
Database:
- New SoftwareSimilarTo model + DbContext configuration + EF migration.
API:
- SoftwareSimilarToDto.
- SoftwareSimilarToController: admin POST/DELETE under /software/similar-to,
normalizing pair ordering and rejecting self-links/duplicates.
- Public GET /software/{id}/similar on SoftwareController, returning the
"other side" of the pair regardless of which column matched.
- Regenerated openapi.yaml and the Kiota-generated Marechai.ApiClient.
Blazor (Marechai):
- SoftwareService: GetSimilarSoftwareAsync/AddSimilarSoftwareAsync/
RemoveSimilarSoftwareAsync.
- Admin SoftwareDialog.razor: new "Similar Software" tab (edit mode only)
with an autocomplete picker excluding self and already-linked rows.
- Public Software/View.razor: new "Similar Software" card in the Overview
tab, loaded alongside Addons.
Uno (Marechai.App):
- SoftwareService (admin) + SoftwareBrowsingService (public): matching
methods backed by the regenerated Kiota client.
- AdminSoftwareViewModel/AdminSoftwarePage: similar-software section with
search-filtered suggestions, mirroring the External IDs pattern.
- SoftwareViewViewModel/SoftwareViewPage: similar-software list with
per-item navigation, mirroring the Base Software/Releases patterns.
- Added SimilarSoftwareHeader/SimilarSoftwareSearchLabel strings to all
five locale .resx files (en, es, fr, de, pt-BR).
This pass is manual curation only; no automated similarity matcher is
included.
Adds a generic ExternalSite + SoftwareExternalId table pair so a Software
can carry identifiers for external cataloging websites, independent from
the existing MobyGames/IGDB mirror/match tables. Backfills existing
matched MobyGames and IGDB rows, and wires new imports/matches to keep
the new table in sync going forward (MobyGames numeric id preferred,
falling back to slug; IGDB slug preferred, falling back to numeric id,
with a new backfill-game-slugs command for previously mirrored rows).
Platforms (Xbox, PlayStation, PC, etc.) were displayed as plain text/chips
with no visual identity. This adds a single optional logo per platform,
shown as a small icon next to the platform name.
Unlike CompanyLogo (which supports multiple SVG logos per company,
versioned by year, for rebrand history), platforms only need one raster
logo with no history, so this introduces a simpler, separate model:
LogoId (Guid?) and LogoExtension (string?) stored directly on
SoftwarePlatform rather than a new child entity.
Backend:
- SoftwarePlatform gains nullable LogoId/LogoExtension columns
(migration AddSoftwarePlatformLogo).
- New POST/DELETE /software/platforms/{id}/logo endpoints (Admin only).
Upload accepts JPEG/PNG/WebP/BMP up to 5MB, validated by extension and
content type. Reuses the existing ImageMagick-CLI-backed Photos helper
(same one used for screenshots/photos) to convert the upload into
JPEG/WebP/AVIF variants under assets/photos/platform-logos/, sized for
an icon (256px full / 64px thumb) rather than the 4K/512px tiers used
for photos. Replacing or deleting a logo cleans up all rendered files.
- SoftwarePlatformDto exposes logo_id/logo_extension; GET list/single
projections include them. Platform cache is invalidated on logo
upload/delete same as other mutations.
- Photos.EnsureCreated for "platform-logos" registered at startup.
Web UI (legacy Blazor):
- SoftwarePlatformDialog (admin edit dialog) gets an upload/preview/
remove logo control, calling the new endpoints directly since there's
only one logo to manage (no separate logo-list page needed).
- Platform chips on the /software landing page and the admin platforms
grid show a small thumbnail next to the name when a logo is set.
Uno app (Marechai.App):
- AdminSoftwarePlatformsViewModel/Page get the same upload/remove logo
capability in the edit panel, using FileOpenPicker and the regenerated
Kiota client. New localized strings (LogoLabel, UploadLogoButton,
RemoveLogoButton, FailedToUploadLogo, FailedToRemoveLogo) added across
all 5 languages (en/de/es/fr/pt-BR).
API client:
- openapi.yaml and Marechai.ApiClient regenerated from a running server
instance to pick up the new endpoints and DTO fields (LogoRequestBuilder,
extended SoftwarePlatformDto). Diff is purely additive.
Continues the prior commit (model/migrations/server) by finishing every
remaining consumer of the SoftwareCompilation entity introduced there.
- Regenerate Marechai.ApiClient from the updated OpenAPI schema: new
SoftwareCompilations request builders and DTOs
(SoftwareCompilationDto, SoftwareCompilationSuccessorDto,
SoftwareBySoftwareCompilationDto, SoftwareVersionBySoftwareCompilationDto),
SoftwareReleaseDto's IsCompilation replaced with SoftwareCompilationId/
SoftwareCompilation. Commit the openapi.yaml snapshot used to drive the
regeneration.
- Marechai (Blazor): add SoftwareCompilationsService and a full admin UI
for the new entity (SoftwareCompilations grid + SoftwareCompilationDialog
with bundled-software/machine pickers, predecessor/relationship type,
and tabs for included software, included versions, sub-compilations,
and releases) plus a public detail page at /software-compilation/{id}.
SoftwareReleaseDialog drops the old IsCompilation checkbox and per-release
included-items management in favor of a SoftwareCompilation autocomplete
picker, since compilation membership now lives on the compilation entity
itself rather than on individual releases. Updated every remaining
IsCompilation/old-junction-DTO reference in release dialogs, the public
release/software view pages, and the suggestion dialog. Search.razor now
routes compilation rows to /software-compilation/{id} instead of a release.
- Marechai.App (Uno): mechanical fixes for the removed IsCompilation
property and old junction DTOs, and the same scope cut as the Blazor
app — included-items management removed from the release editor and
read-only release view (that UI now belongs solely on the compilation),
with the now-dead helper methods and XAML sections removed.
- Marechai.MobyGames: CompilationRelationService and ImportService now
create a real SoftwareCompilation row instead of flipping a release flag,
and resolve each contained slug to either a Software or an existing
SoftwareCompilation. This fixes a long-standing bug where a slug
previously converted into a compilation had no replacement pointer
recorded on MobyGamesImportState, so re-encountering it as a nested
member of another compilation could never resolve. Nested compilations
are now linked via SoftwareCompilationBySoftwareCompilation.
StateService.MarkImportedAsync gained a softwareCompilationId parameter,
and OrphanCleanupService's software-merge dedup pass was re-keyed from
the old release-keyed junction to SoftwareBySoftwareCompilation.
Full solution build is clean with no pending EF model changes against the
three migrations from the prior commit.
Software entries (especially games) often have regional, script, or
otherwise alternative titles — e.g. a Japanese Kanji/Romaji title
alongside the Western release name. There was previously no way to
record these.
Database layer:
- New SoftwareAlternativeTitle entity (SoftwareId FK, Title, nullable
Comment), cascade-deleted with its Software.
- New SoftwareAlternativeTitleCommentTranslation, a string-pool
translation table keyed by (CommentText, LanguageCode), mirroring
the existing SoftwareCoverCaptionTranslation pattern. Comments are
free text that repeats often ("Japanese title", "European title"),
so pooling avoids duplicate translation work.
- Migration AddSoftwareAlternativeTitles.
Translation pipeline:
- SoftwareAlternativeTitleCommentTranslationProvider, registered with
the background TranslationWorker, anti-join translates missing
comments into es/fr/de/it/nl.
API:
- GET /software/{id}/alternative-titles (public, language-aware, with
an English fast path that skips the translation sub-query).
- POST/PUT/DELETE /software/alternative-titles (admin-only CRUD) on a
new SoftwareAlternativeTitlesController.
- Kiota client regenerated against the updated OpenAPI schema.
Blazor admin UI:
- New "Alternative Titles" tab in SoftwareDialog.razor, with the same
create-mode buffering/flush-after-create pattern used by the
existing Genres tab.
- SoftwareService.cs client methods for list/add/remove.
- Localized resx strings added for en/es/fr/de/it/nl.
IGDB matching:
- Mirrored IGDB's /alternative_names endpoint into a new
IgdbAlternativeName table (GameIgdbId, Name, Comment), via
IgdbAlternativeNameMirrorService and a new `mirror-alternative-names`
CLI command, following the existing resumable id-paged mirror
pattern (e.g. InvolvedCompanyMirrorService).
- GameMatcher now builds a full name-variant set per side (IGDB
canonical name + its mirrored alternative names; local Software
name + its SoftwareAlternativeTitle rows) and matches across the
full cross-product, both for exact equality and the Jaro-Winkler
fuzzy sweep. This lets an IGDB alternate name match a local
alternate title directly, not just canonical-vs-canonical.
- Match results record whether the winning pair was canonical-vs-
canonical or involved an alternate, via a "-alt" suffix on
MatchType (exact-alt, exact-platform-disambiguated-alt,
jw-platform-corroborated-alt), keeping the match audit trail
informative.
Software successors were previously undifferentiated (only the first
one was even exposed by the API). Add a SoftwareRelationshipType enum
(Sequel/Fork/Remake/Remaster/Port), default existing rows to Sequel,
and group all successors by relationship type on the software page.
Covers were being attached to the nearest existing SoftwareRelease even when
no release actually matched the cover's region, e.g. a Spanish cover landing
on a Japanese release just because it was the only release present. The
importer no longer guesses: it stops trying to attach covers to a release at
all, clustering them by Software + MobyGames cover-group id instead.
SoftwareReleaseId becomes an optional, admin-only manual curation field, with
a new bulk "assign whole group to a release" action in the cover admin UI. A
new repair-covers command retroactively detaches previously misassigned
covers using data already recorded during import, no re-scraping needed.
Implement a new feature where users receive 5 invitation codes when they confirm their email.
Backend changes:
- Extend InvitationCodeGenerator with GenerateForOwnerAsync() for batch code creation
- Mint 5 codes on email confirmation (not registration) in AuthController.ConfirmEmailAsync
- Add GET /invitation-codes/mine (self-service) endpoint for users to view their own codes
- Add POST /users/{id}/invitation-codes (admin-only) endpoint to grant additional codes
- Create MyInvitationCodeDto and GrantInvitationCodesRequest DTOs
Frontend changes:
- Add "My Invitation Codes" tab to Profile.razor with list of user's codes
- Unused codes rendered normally with copy-to-clipboard button
- Used codes shown with strikethrough (no user identity revealed)
- Add "Give Invitation Codes" action to admin Users page
- Dialog prompts for count (1-50) of codes to grant to selected user
- Success/error messages displayed after granting
- Extend InvitationCodesService.GetMyAsync() and UsersService.GrantInvitationCodesAsync()
- Regenerate Kiota API client from updated OpenAPI schema
No database schema changes required - existing InvitationCode fields accommodate both
registration-issued and admin-granted codes.
- Implemented MachinePromoArtService for handling promo art operations including upload, update, and delete.
- Added new JavaScript helper for batch uploading promo art images.
- Enhanced localization files for Spanish, French, Italian, and Dutch with new promo art related strings.
- Registered MachinePromoArtService in the service container.
- Updated UI to support promo art management functionalities.
- Implemented DuplicateRequestBuilder to handle duplication requests for Wwpc imports.
- Added DuplicateAsync method in WwpcPromotionService to create a copy of a WwpcSoftware entry with a new name.
- Introduced DuplicateWwpcImportDto for request body validation.
- Updated WwpcImportsController to include a new endpoint for duplicating Wwpc imports.
- Enhanced WwpcImportsQueue to allow duplication of entries via a dialog interface.
- Created WwpcDuplicateDialog component for user input on duplicate names.
- Updated kiota-lock.json and WwpcPendingDtos.cs to reflect new DTOs and service methods.
- Implemented a new RankingsIndex API to fetch overall, genre, and platform rankings in a single call.
- Updated Rankings.razor.cs to utilize the new RankingsIndex API and reorganized ranking data into separate lists for better management.
- Enhanced the View.razor and View.razor.cs to display ranking placements for software titles, including overall and genre/platform specific rankings.
- Added new UI components in View.razor to show ranking chips for top-250 placements.
- Improved localization by adding new resource entries for rankings in multiple languages (en, de, es, fr, it, nl).
- Simplified navigation in NavMenu.razor by removing unnecessary grouping for software links.
- Implemented WwpcImportReviewDialog for reviewing pending software imports from WinWorldPC.
- Created WwpcImportsQueue page for managing the import queue with filtering options.
- Added WwpcImportsService to handle API interactions for WinWorldPC imports.
- Updated navigation menu to include a link to the WinWorldPC Imports page.
- Added localization support for the new imports feature in German, Spanish, French, Italian, and Dutch.
- Introduced OldDosImportsService for handling old-dos.ru imports, including methods for fetching pending items, accepting, skipping, and discarding imports.
- Added new resource files for localization in Dutch, German, Spanish, French, and Italian for the OldDos imports queue and review dialog.
- Updated AdminNavMenu to include a link to the new OldDos imports management page.
- Added SoftwareScreenshotsBatchUploadDialog component for managing batch uploads of software screenshots.
- Integrated JavaScript helper for handling file uploads with progress tracking and error handling.
- Updated _Host.cshtml to include the new JavaScript file for batch uploads.
- Enhanced SoftwareService with methods for committing and managing screenshot uploads.
- Added localization strings for batch upload messages in multiple languages (en, de, es, fr, it, nl).
- Implemented methods to delete admin-staged pending photos and commit batches for GPU, Machine, Processor, and Sound Synth photos in their respective services.
- Added JavaScript helpers for batch uploading photos via XHR for GPU, Machine, Processor, and Sound Synth admin pages, including progress tracking and error handling.
- Each upload helper supports multiple image formats and enforces size limits.
- Added Identify method in Photos class for content-sniffing image formats and dimensions using ImageMagick.
- Introduced GenerateThumbnailJpeg method for creating JPEG thumbnails from images.
- Updated Program.cs to register BatchUploadJobStore and BatchUploadJobReaperService for managing batch upload jobs.
- Refactored SoftwareCovers.razor to replace single image upload with a batch upload dialog.
- Created SoftwareCoversBatchUploadDialog.razor and its code-behind for handling batch uploads, including staging, committing, and displaying upload progress.
- Added JavaScript helper (software-cover-batch-upload.js) for managing XHR-based uploads with progress reporting.
- Enhanced SoftwareReleasesService with methods for deleting pending covers and committing batch uploads.
- Updated _Host.cshtml to include the new JavaScript file for batch uploads.
- Implemented SoftwareScreenshotGroupTranslationProvider to handle translations for software screenshot groups.
- Integrated the provider into the service container in Program.cs.
- Enhanced SoftwareScreenshotSuggestionApplier to support optional group names for screenshots.
- Updated SoftwareScreenshots.razor and SoftwareScreenshots.razor.cs to include group name selection in the UI.
- Modified SoftwareService to handle canonical group names during screenshot uploads.
- Added autocomplete functionality for group names in various dialogs and suggestion panels.
- Updated localization resources for new group name fields and messages.
- Added a merge button in the Companies admin page to initiate the merge process.
- Implemented the OpenMergeDialog method to handle the opening of the merge dialog.
- Created a new CompanyMergeDialog component to facilitate the merging of companies.
- Implemented logic to preview the merge, including field differences and related data that will be moved.
- Added methods in CompaniesService to fetch merge previews and execute the merge operation.
- Updated localization resources to include new strings related to merging companies.
- Implemented LinkBaseSoftwareDialog for linking orphan add-ons and DLCs to their base software.
- Created SoftwareOrphanAddons page to display orphan add-ons with options to link them.
- Added server-side methods in SoftwareService for fetching orphan add-ons and counting them.
- Enhanced search functionality with filtering options for orphan add-ons.
- Updated navigation menu to include a link to the orphan add-ons management page.
- Added localization strings for new features and UI elements.
- Implemented a new page for managing pseudoduplicate software entries at /admin/software/duplicates.
- Introduced a new SQL function NormalizeForDuplicate to handle name normalization for duplicate detection.
- Enhanced the SoftwareService to support fetching duplicate groups and their counts.
- Added UI components for filtering and displaying duplicate software groups, including pagination and actions for merging duplicates.
- Updated localization files for new UI strings in multiple languages.
- Modified the admin navigation menu to include a link to the new duplicates management page.
- Added GenresBySoftwareRequestBuilder to handle requests for linking software to genres.
- Created WithSoftwareItemRequestBuilder and WithGenreItemRequestBuilder for item-specific operations.
- Introduced SoftwareGenreLinkDto for POST requests to link software and genres.
- Developed SoftwareGenresController for admin CRUD operations on genre links.
- Enhanced SoftwareService with methods to add and remove genre links.
- Updated SoftwareDialog and SoftwareDialogResult to manage genre links during software creation.
- Added localization for genre-related messages in multiple languages.
- Introduced NewMessageEmail.cshtml and NewMessageEmailModel.cs for email template and model.
- Implemented endpoints in AuthController for retrieving and updating user notification preferences.
- Enhanced MessagesController to queue email notifications for new messages.
- Added LastLanguageCaptureMiddleware to capture user's language preferences.
- Created MessageNotificationQueue and MessageNotificationWorker to handle email sending in the background.
- Updated Profile.razor and Profile.razor.cs to manage notification preferences in the user profile.
- Localized new strings for notification preferences in multiple languages.
- Updated AuthService to handle fetching and updating notification preferences.
- Modified HttpAuthHandler to forward user's current UI culture for localization.
- Introduced a new migration to create the SoftwareScreenshotCaptionTranslations table for storing translated captions of software screenshots.
- Updated MarechaiContext to include a DbSet for SoftwareScreenshotCaptionTranslation.
- Implemented the SoftwareScreenshotCaptionTranslation model with necessary properties and relationships.
- Enhanced SoftwareScreenshotsController to support fetching localized captions based on the requested language.
- Added SoftwareScreenshotCaptionTranslationProvider to handle background translation tasks for missing captions.
- Updated SoftwareService to pass the UI language when fetching screenshots.
- Updated the method for fetching software credits to include language parameter based on the UI language.
- Modified the GetCreditsRolesAsync method to return a list of SoftwareCreditRoleDto instead of strings, allowing for localized display values and canonical English values for roles.
- Enhanced the credits suggestion dialog to utilize established roles while accepting custom entries.
- Created migration to add SoftwareCoverCaptionTranslations table with necessary fields and constraints.
- Updated MarechaiContext to include DbSet for SoftwareCoverCaptionTranslation.
- Implemented SoftwareCoverCaptionTranslation model to represent translations for software cover captions.
- Enhanced SoftwareCoversController to support fetching localized captions based on language.
- Introduced SoftwareCoverCaptionTranslationProvider for managing translation logic and background processing.
- Updated admin UI to allow editing of canonical captions, reflecting changes in localized captions.
- Modified SoftwareService to include language parameter when fetching software covers.
- Introduced SoftwarePromoArtGroupTranslations table to store localized names for software promo art groups.
- Updated MarechaiContext to include DbSet for SoftwarePromoArtGroupTranslation.
- Implemented translation provider for SoftwarePromoArtGroup to handle localization.
- Enhanced SoftwarePromoArtController to support fetching localized group names based on user language.
- Updated various UI components to handle localized group names and ensure correct mapping to canonical names.
- Added translations for "Other (promo art)" in multiple languages.
- Updated SoftwareAttributesService to include language parameter in DistinctKeys and DistinctValues API calls.
- Enhanced SoftwareService to pass language parameter when fetching software specifications and attributes for specific software and releases.
- Removed a large number of unused genre entries from French and Italian resource files for SoftwareService.
- Updated SoftwareService to include language parameter when fetching genres from the API.
- Removed TranslationService registration from the DI container in Register.cs.
- Added MarechaiTranslation service registration in Startup.cs for improved translation handling.
- Updated _Imports.razor to include the new Translation namespace.