Skip to content

feat(files): add per-user file favorites#191

Closed
zjean wants to merge 36 commits intoSync-in:mainfrom
zjean:upstream-contrib/favorites
Closed

feat(files): add per-user file favorites#191
zjean wants to merge 36 commits intoSync-in:mainfrom
zjean:upstream-contrib/favorites

Conversation

@zjean
Copy link
Copy Markdown
Contributor

@zjean zjean commented May 8, 2026

Summary

  • Adds a files_favorites join table (userId, fileId, createdAt) with cascade deletes on both FKs and a composite primary key
  • New backend service + 3 controller endpoints: GET /spaces/favorites, POST /spaces/favorite, DELETE /spaces/favorite/:fileId
  • isFavorite: boolean included in file browse results via a new withIsFavorite option on browseFiles (EXISTS subquery, same pattern as withHasComments)
  • Favorites list is filtered by the user's current space/share access — mirrors getRecentsFromUser pattern
  • New Angular favorites module: constants, routes, page component, widget (matches recents styling), FileFavoriteModel
  • Star toggle column as first column in the file browser table
  • Favorites sidebar menu entry with active-state routing
  • POST /spaces/favorite accepts full file context (FavoriteFileDto) and performs a find-or-create on the files table so unindexed filesystem entries (negative inode IDs) can be starred immediately — same pattern as share creation

Database

CREATE TABLE files_favorites (
  userId   BIGINT UNSIGNED NOT NULL REFERENCES users(id) ON DELETE CASCADE,
  fileId   BIGINT UNSIGNED NOT NULL REFERENCES files(id) ON DELETE CASCADE,
  createdAt DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
  PRIMARY KEY (userId, fileId),
  INDEX user_idx (userId),
  INDEX file_idx (fileId)
)

API

Method Route Description
GET /api/app/spaces/favorites List favorites (filtered by current access)
POST /api/app/spaces/favorite Add favorite (find-or-create file record)
DELETE /api/app/spaces/favorite/:fileId Remove favorite

Test plan

  • Star a personal file → appears in Favorites page, icon filled
  • Star a file inside a space → appears in Favorites page with space icon
  • Remove a favorite → disappears from Favorites page immediately
  • Remove user from a space → space files no longer appear in their Favorites list
  • Delete a space or share → cascade removes all related favorites automatically
  • Trash a file → does not appear in Favorites list
  • Star a brand-new (unindexed) file → gets indexed and starred in one request

zjean and others added 30 commits May 7, 2026 19:16
Introduces the files_favorites table (userId + fileId composite PK,
cascade deletes, user_idx index) and a fileIsFavoriteForUserSQL helper
for use in query-layer EXISTS checks.
Thin service that delegates getFavorites/addFavorite/removeFavorite to
FilesQueries; registered in FilesModule providers and exports alongside
FilesRecents.

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
…igation

Introduces FileFavorite interface with a server-computed navPath (personal/
space-alias/path) so goToFile can navigate correctly instead of always
landing at personal root. Also copies mime icon styles into the widget scss.

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
- Add isFavorite = false to FileModel states (populated via Object.assign from backend)
- Enable withIsFavorite: true in spaces.controller.ts browseSpace call
- Thread withIsFavorite through SpacesBrowser.browse, parseDB, and updateDBFiles
- Import faStar (solid) and faStarRegular (regular) into spaces-browser component
- Add toggleFavorite() method calling filesService.toggleFavorite with optimistic local flip
- Render star badge before comments badge in file row info column
…navPath display, loadFavorites set(), fileId index, migration NULL guard, ParseIntPipe on limit, breadcrumb URL, limit cap

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
- Remove unused FAVORITES_TITLE import from favorites.component.ts
- Add overflow: hidden to .recent-item-card__media in widget scss
- Use sql<boolean>.mapWith(Boolean) for isFavorite in getFavorites; update FileFavorite interface to boolean
- Filter trashed files from getFavorites with eq(files.inTrash, false)
- Accessibility: alt text on mime icon img, aria-label on toggle and file buttons in widget
- Accessibility: make star toggle in spaces-browser keyboard-accessible (keydown.enter/space, role, tabindex, aria-label, aria-pressed)
- Spec: assert NotFoundException type in addFavorite not-accessible test

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
…re indexing pattern

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
@zjean zjean closed this May 8, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant