Commit Graph
156 Commits
Author SHA1 Message Date
REDCODE 63e3413141 perf(asset): server-side seeded shuffle, DTO projection, opt-in count
Random ordering now uses a pg_catalog.md5-based deterministic shuffle in SQL
instead of materializing every matching asset ID in memory. The listing
projects directly to AssetPreviewDto (no full entity transfer) with album and
cosplayer names via one grouped query. includeCount=false skips the count
query entirely.
2026-08-11 13:10:16 +02:00
REDCODE b83b97934c perf(album): project assets to DTO in SQL and slim Find paths
FindVisible filters and projects assets at the database level instead of
loading full entities then filtering/sorting in memory. Find no longer
eager-loads assets; FindWithAssets covers the update path. SearchQuery gains
personOwnerId filter and AsNoTracking.
2026-08-11 13:09:48 +02:00
REDCODE 6bf627e429 perf(person): push album filter/search/sort/pagination to SQL in FindVisible
No longer loads the person's full album/asset graph into memory; uses ID-first
paging, ILike search against the trgm GIN index, and DB-level counts. Slims
Find() to a PK lookup for write paths.
2026-08-11 13:09:44 +02:00
REDCODE 0480b00766 fix: desloppify review fixes - PathFromGuid extraction, RegexHighlighter dedup, port default fix
- Extract PathFromGuid to shared PathUtils.cs (PreviewJob + ThumbnailJob)
- Consolidate RegexHighlighter duplicate methods via delegation
- Fix default port 3306 -> 5432 in appsettings.json + docker-compose.yml
- Remove port gotcha section from AGENTS.md
- Fix ITagRepository XML docs after parameter rename (id -> tag)
2026-07-29 20:12:58 +02:00
REDCODE 26674e05e5 refactor: quality improvements from desloppify scan
- Remove 16 unused using System; imports from migration files (implicit usings available)
- Fix 6 empty catch blocks with proper exception logging
- Remove unused import (false positive flagged as wontfix)
- Skip test coverage, orphaned, and stale exclude issues as false positives
- Add missing namespace to IPersonRepository.cs
- Rename IFolderRepository Create/Delete to Insert/Remove for CRUD consistency
- Fix ITagRepository.Delete parameter name from 'id' to 'tag'
- Add missing IDisposable to IMediaRepository
- Rename SettingsExtensions to SettingsExtension for naming consistency
- Rename PagedParametersDTO.cs to PagedParametersDto.cs
- Add .desloppify/ to .gitignore
2026-07-29 19:52:49 +02:00
REDCODE 79252d363d fix: sort album assets by OriginalFilename instead of CreatedAt (#144) 2026-07-21 15:55:58 +02:00
REDCODE 8ff21fa56b fix: use pre-pagination album count (#143)
Add AlbumTotalCount [NotMapped] to Person model, set before pagination in FindVisible, read in PersonMapper instead of paginated Albums.Count. Also apply inline aspect-ratio only in masonry mode so grid mode uses CSS aspect-ratio: 1 for square crops.
2026-07-21 15:47:21 +02:00
REDCODE 439df56f2b fix: use per-card aspect-ratio from cover dimensions for stable masonry layout (#146) 2026-07-21 15:05:02 +02:00
REDCODE 9965a13f40 fix: order album assets by CreatedAt in album detail (#144) 2026-07-21 14:26:29 +02:00
REDCODE d89f6f0a21 fix: address PR #142 review issues
- Fix timer leak: implement IDisposable on AlbumAssetPicker
- Remove unnecessary full-table COUNT in GetFolderGroups
- Remove dead DirectoryEntryDto.AssetCount
- Remove unused ExistingAssetIds parameter
- Remove redundant .Distinct() after .Union()
- Extract hardcoded page size 150 to named constant
- Rename AssetSearchOptionsDto.Unassigned to Unlinked for consistency
- BrowseAssets returns 404 for missing folderId instead of 200 empty
2026-07-17 00:00:37 +02:00
REDCODE 8589a85625 refactor: rename UnlinkedAssetGroupDto to AssetGroupDto 2026-07-16 23:14:14 +02:00
REDCODE 983cd8baef refactor: add Embedded mode, UnlinkedOnly param, rename methods from Unlinked to generic names 2026-07-16 23:13:18 +02:00
REDCODE eae6152738 fix: filter directory names to only paths containing a slash after prefix, excluding filenames 2026-07-16 22:56:17 +02:00
REDCODE c4788e1572 fix: use parameterized SqlQueryRaw instead of unquoted string.Format to prevent SQL error 2026-07-16 22:51:41 +02:00
REDCODE 1dfa9eaf41 perf: replace in-memory path parsing with SELECT DISTINCT SQL, remove dir count badges 2026-07-16 22:44:20 +02:00
REDCODE 50b5a28841 perf: replace in-memory path parsing with SQL SPLIT_PART aggregation and double-LIKE filter 2026-07-16 22:15:11 +02:00
REDCODE 5d9aa21987 feat: add file-browser BrowseUnlinkedAssets endpoint with directory tree from OriginalPath 2026-07-16 21:43:44 +02:00
REDCODE 90c81c2d20 fix: use Albums.Count==0 instead of !Any() for unlinked filter, add trace logging 2026-07-16 21:06:18 +02:00
REDCODE 22706a09cd feat: extend AssetRepository with unassigned, folderId, uploadedBy, search filters and GetUnlinkedGroups 2026-07-16 20:51:34 +02:00
REDCODE 1e2bc83b20 fix: guard against null PersonOwnerId in MergePeople albums query
Replace PersonOwnerId ?? Guid.Empty with explicit null check to
avoid matching albums with no owner if sourceIds contained Guid.Empty.
2026-07-16 18:30:23 +02:00
REDCODE 656e6fa9bf feat: add MergePeople to person repository
Reassigns albums (PersonOwnerId), faces (PersonId), and maintainers
from source people to destination, then hard-deletes source people.
2026-07-16 17:56:24 +02:00
REDCODE 5e1306e466 feat: add MergeAlbums to album repository
Loads destination + source albums with assets, moves all assets
from sources to destination (deduplicated), then hard-deletes sources.
2026-07-16 17:55:46 +02:00
REDCODE 7db1738678 fix: soft-delete users in UserController.Delete instead of hard-delete
UserController.Delete was calling userRepository.Delete(user) which performed
a hard delete (context.Users.Remove), violating the project's soft-delete
convention. Changed to set user.DeletedAt = DateTime.UtcNow, matching the
pattern used in AssetController.Delete.

Also removed the now-unused Delete method from IUserRepository and
UserRepository for consistency with AssetRepository (which also has no
Delete method).

Extended REST tests 103-104 to verify deletedAt is set after deletion.

Closes #130
2026-07-16 02:31:52 +02:00
REDCODE 3bf98cbfed fix: prevent private assets with null UploadedBy leaking to anonymous users
Adds userId.HasValue guard to the private-visibility condition in both
AssetRepository and MediaRepository, preventing the null==null match
when an anonymous visitor (userId=null) encounters an asset with null
UploadedBy.
2026-07-15 23:33:08 +02:00
REDCODE 86f95d7461 feat: add role-visible colored borders on albums and assets based on item state (#78)
Backend:
- Add DeletedAt to AssetPreviewDto, AlbumAssetPreviewDto, AlbumPreviewDto
- Remove redundant DeletedAt from AssetDto (now inherited from base)
- Add viewerId parameter to ToAssetPreviewDto, ToAlbumPreviewDto,
  ToAlbumFullDto, ToPersonDetailedDto mappers
- Conditionally send DeletedAt only when viewer is Admin or asset uploader
- Pass uid/viewerId from all controller/repository call sites

Frontend:
- AlbumCard: show orange (not public) / red (deleted) border in select
  mode for admins/curators
- AlbumDetail: same border logic in GetTileClass for asset tiles
- Borders only appear in select mode per REDCODE's feedback
2026-07-15 20:48:00 +02:00
REDCODE 758696f7c9 fix: add entity-level visibility gating for albums and persons
Adds R3/R4 visibility gates to AlbumRepository.FindVisible and
PersonRepository.FindVisible so that 404 is returned when the
requesting user lacks permission to see the entity itself.

Adds comprehensive HTTP tests verifying:
- R2: anonymous asset detail access
- R3: Public/Protected visible to User, Private returns 404 (Admin sees all)
- R4: Private person returns 404 for User, 200 for Admin/Curator
2026-07-15 20:30:06 +02:00
REDCODE 6efccae030 fix: address PR #127 review issues — fallback, eager load, indentation, Maintainer tests
- Change ?? EAccessLevel.Admin fallback to ?? EAccessLevel.User in UserController.GetAll
- Add .Include(u => u.MaintainedPersons) to UserRepository.GetAll() so MaintainedPersonIds/MaintainedPersonNames populate in list endpoint
- Fix UsersMapper.cs method body indentation (was flush with class)
- Add Maintainer-role visibility tests (68.5-68.8): promote user, test viewing admin, demote back
2026-07-15 17:31:33 +02:00
REDCODE 4f3e3a28cc fix: enforce data visibility per access level with test coverage
- UsersMapper.ToGetUsersDto: filter Email, BannedAt, DeletedAt, AccessLevel,
  MaintainedPersonIds by viewer access (admin/self only)
- AssetsMapper.ToFullAssetsDto: pass viewerId to restrict UploadedBy.Email;
  null-safe Uploader fallback
- AssetsMapper.ToAssetPreviewDto: gate FileName behind Curator+ (was public)
- AlbumMapper: gate FileName behind Curator+; fix AssetCount to count only
  non-deleted assets
- AssetRepository.FindVisible: include Uploader to prevent NRE (500 error)
- AssetController: restrict UploadedBy reassignment to Admin only
- UserController: pass viewer context to all ToGetUsersDto calls
- WepApiTest.http: add 18 data visibility tests (#64-#81) verifying field
  gating per role; fix 3 pre-existing stale assertions; avoid jsonPath NPE
  on null values by using response.body directly
2026-07-15 14:53:39 +02:00
REDCODE 358bf0918e feat: maintainer assignment UI for cosplayers, maintainer display on user page
Backend:
- Add MaintainerUserIds to PersonUpdateDto/PersonDetailedDto
- Add MaintainedPersonNames to UserInfoDto
- Add IPersonRepository.SetMaintainers + implementation
- PersonRepository.Find/FindVisible now include Maintainers
- PersonController.Update handles maintainer assignment (admin/curator)
- Fix PersonController.Update to save Visibility field
- Allow curators to list all users via UserController.GetAll

Frontend (PersonForm):
- Add maintainer search+select UI (admin/curator only in edit mode)
- Search users client-side, click to add, badge with X to remove
- Saves maintainer IDs on person update

Frontend (CosplayerDetail):
- Show 'Maintained by: user1, user2' in header with clickable links
- Pass maintainer data to PersonForm

Frontend (User page):
- Show 'Maintainer of: cosplayer1, cosplayer2' with links
- Non-admin view: strip Email, Created, Deleted fields
- Fix LoadUser to use route UserId instead of logged-in user
2026-07-15 13:11:05 +02:00
REDCODE 656784af00 feat: maintainer cosplayer assignment in admin user edit modal
Backend:
- Add MaintainedPersonIds to UserUpdateDto
- Add SetMaintainedPersons to user repository (removes old PersonMaintainer rows, inserts new)
- Wire up in UserController.Update (admin only)

Frontend:
- Add PersonService injection to AdminUsers page
- Searchable cosplayer dropdown in edit modal for Maintainer users
- Click result to add, badge with X to remove
- Zero debounce, saves only on Submit
- Pre-loads currently assigned cosplayer names on modal open
2026-07-15 12:55:03 +02:00
REDCODE f32d0678df feat: add MaintainedPersonIds to UserInfoDto with backend plumbing 2026-07-15 11:22:33 +02:00
REDCODE 88a0098314 perf: add AsSplitQuery to repository queries with multiple Include calls 2026-07-14 22:00:33 +02:00
REDCODE e1504dbf72 style: standardize access level comparison to switch expressions
- AlbumRepository.SearchQuery: if/else → switch expression
- PersonRepository.FindVisible: nested if → switch statement with guarded case
- AlbumController Update/BulkDelete/Delete: if/else → switch
2026-07-14 21:29:46 +02:00
REDCODE b67b2260fe docs(repo): fix stale pageSize default in IAlbumRepository XML doc
Actual default is PagedParametersDto.MaxPageSize (changed in 08c3890),
but documentation still referenced the old hardcoded 150.
2026-07-14 21:27:15 +02:00
REDCODE 06fd26e641 refactor: move all visibility filtering from mappers and controllers to repositories
- AlbumMapper: remove CountVisibleAssets(), drop userId param from ToAlbumPreviewDto()
- PersonMapper: remove IsAssetCountVisible(), drop userId param from ToPersonDetailedDto()
- AssetRepository: add FindVisible(), add userId/accessLevel params to GetAssets()
- AlbumRepository: add FindVisible(), replace .Include(a => a.Assets) with subquery count in SearchQuery()
- PersonRepository: add FindVisible() with visibility filtering + album search/sort/pagination
- AssetController.Get: replace inline switch with FindVisible()
- AssetController.GetAll: pass uid/accessLevel to GetAssets(), remove inline filter
- AlbumController.Get: replace inline asset filter with FindVisible()
- PersonController.Get: replace 50-line inline filter/search/sort/page with FindVisible()
2026-07-14 21:17:02 +02:00
REDCODE 74c74cbbdf fix(mapper): scope asset counts in album/ person previews to visible assets
- Added optional userId parameter to ToAlbumPreviewDto and ToPersonDetailedDto
- AlbumRepository.SearchQuery and PersonMapper now pass userId for visibility-scoped counts
- ToAlbumFullDto unchanged (callers pre-filter assets before mapping)
- Private helper filters out non-visible assets for non-curator users
2026-07-14 20:44:52 +02:00
REDCODE 774ee0d7c7 fix(media): allow Maintainer to access media files for cosplayers they maintain
- Injected IPersonRepository into MediaRepository
- Updated GetVisibleAsset with Maintainer-specific check via album→person→maintainer chain
2026-07-14 20:42:55 +02:00
REDCODE d4cd305c28 fix(controller): scope AssetController maintainer permissions via album→person chain
- Added IPersonRepository injection into AssetController
- Added FindWithAlbums to IAssetRepository/AssetRepository for single-entity checks
- Modified FindBulk to include Albums navigation property
- Single Update/Delete: Maintainers can act on assets in albums of maintained persons
- BulkUpdate/BulkDelete: same scope expansion for Maintainers
2026-07-14 20:35:55 +02:00
REDCODE 671783fd16 fix(repo): scope AlbumRepository.SearchQuery maintainer visibility to maintained persons
Maintainers now only see Private albums for persons they maintain,
matching the same pattern already used in PersonRepository.SearchQuery.
2026-07-14 20:33:38 +02:00
REDCODE b9251c3e19 style(repo): use explicit named enum cases in PersonRepository switch expressions 2026-07-14 19:22:14 +02:00
REDCODE 0d1ce9feb3 style(repo): expand switch expression arms for readability 2026-07-14 19:14:35 +02:00
REDCODE 0673017765 style(repo): convert visibility filters to switch expressions for readability 2026-07-14 19:13:20 +02:00
REDCODE 08c389026a fix(repo): use PagedParametersDto.MaxPageSize const instead of hardcoded 150
Replaced magic number with named constant in both interface and implementation.
2026-07-14 19:03:02 +02:00
REDCODE fe33fe2044 fix(stats): add ProtectedAssets count, fix PrivateAssets to count only Private
Previously PrivateAssets counted everything != Public (i.e., Protected+Private combined).
Now PublicAssets=Public, ProtectedAssets=Protected, PrivateAssets=Private.
2026-07-14 19:02:07 +02:00
REDCODE a449c792c1 fix(media): replace bool parameter with EAccessLevel, add deleted asset check
- GetVisibleAsset: accept EAccessLevel enum instead of bool isCuratorOrAbove
- Admin: sees all assets including deleted
- Curator: sees own deleted + all non-deleted
- Maintainer/User: only non-deleted + visibility filtering
2026-07-14 19:01:39 +02:00
REDCODE c64c1947e6 fix(repo): scope Maintainer to only maintained persons in GetAllVisible and SearchQuery
Previously Maintainer saw all people like Curator+. Now:
- Curator+: sees all people
- Maintainer: sees Public + Protected + persons they maintain
- User: sees Public + Protected only
2026-07-14 18:58:14 +02:00
REDCODE 1b444dab93 feat(auth): complete Maintainer role — add PersonMaintainer checks, update controller authz, pass uploadedBy search param
- AlbumSearchParametersDto: add UploadedBy field
- IPersonRepository/PersonRepository: add IsMaintainerOf method
- AlbumController: allow Maintainer to update with scope check, pass uploadedBy to SearchQuery
- PersonController: allow Maintainer to update maintained persons
- AssetController: allow Maintainer to update/delete own assets
- TagController: allow Maintainer to CRUD all tags
- Fix Authorize(Roles) to include Maintainer where appropriate
- Update WepApiTest.http: curator accessLevel 1→2
2026-07-14 18:13:15 +02:00
REDCODE 2335e8e23a feat(repos,controllers): update repositories, interfaces, controllers for auth redesign — visibility filtering, UploadedBy/Uploader rename, Maintainer support
- PersonRepository, AlbumRepository, AssetRepository, MediaRepository, StatsRepository: visibility-based filtering
- IAlbumRepository, IAssetRepository, IMediaRepository: updated signatures (FindByUploader, uploadedBy param, accessLevel)
- AlbumController, AssetController, PersonController, MediaController: replace IsPubliclyShared/SharedWith/UserOwnerId/Owner with Visibility/UploadedBy/Uploader
- FileSystemCrawlJob: IsPubliclyShared=false → Visibility=EVisibility.Private
- Asset model: nav property UploadedBy→Uploader to avoid FK/nav name collision
2026-07-14 17:44:46 +02:00
REDCODE be7532dd74 fix: implement CosplayersMissingProfile stat
Replaces the hardcoded zero with a real query counting people
without a profile picture (ProfileAssetId == null).

Renamed from CosplayersMissingCover to CosplayersMissingProfile
to reflect the actual metric.

DTO, repository, and UI label updated accordingly.

fixes #97
2026-07-14 11:40:06 +02:00
REDCODE dbe55be63f refactor(albums): extract visibility-aware AssetCount into AlbumMapper overload
Replace inline AlbumPreviewDto construction in AlbumRepository.SearchQuery
with a call to the new ToAlbumPreviewDto(album, userId, accessLevel) mapper
overload, making the visibility logic reusable and the repository more compact.
2026-07-12 21:15:00 +02:00