feat(media-supply): persist favorites with multi-group membership

Move media favorites from client state to PostgreSQL with server-side
search and pagination.

- Store favorites keyed by (tenant, user, group, resource) so a resource
  can belong to multiple groups; the 500-resource cap counts distinct
  resources, not memberships.
- Add favorite-group CRUD and group/global removal routes, with silent
  cleanup of delisted or invisible resources on read.
- Serve favorite resource details server-side, 10 per page, with name/ID
  search; favorites-page removal is group-scoped, resources-page removal
  is global.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-07-10 20:10:50 +08:00
parent 0bf4b73ebc
commit 67a6cbcd9e
15 changed files with 2378 additions and 518 deletions
@@ -127,6 +127,13 @@ func RegisterRoutes(app *bootstrap.App) {
mediaSupply.GET("/resources", mediaSupplyHandler.ListResources)
mediaSupply.GET("/resources/by-ids", mediaSupplyHandler.ListResourcesByIDs)
mediaSupply.GET("/resources/search-options", mediaSupplyHandler.SearchOptions)
mediaSupply.GET("/favorite-groups", mediaSupplyHandler.ListFavoriteGroups)
mediaSupply.POST("/favorite-groups", mediaSupplyHandler.CreateFavoriteGroup)
mediaSupply.DELETE("/favorite-groups/:group_id", mediaSupplyHandler.DeleteFavoriteGroup)
mediaSupply.GET("/favorite-groups/:group_id/resources", mediaSupplyHandler.ListFavoriteResources)
mediaSupply.PUT("/favorite-groups/:group_id/resources/:resource_id", mediaSupplyHandler.PutFavoriteResource)
mediaSupply.DELETE("/favorite-groups/:group_id/resources/:resource_id", mediaSupplyHandler.DeleteFavoriteResourceFromGroup)
mediaSupply.DELETE("/favorite-resources/:resource_id", mediaSupplyHandler.DeleteFavoriteResource)
mediaSupply.POST("/sync-jobs", mediaSupplyHandler.QueueSync)
mediaSupply.PUT("/resources/:id/price", mediaSupplyHandler.SetResourcePrice)
mediaSupply.POST("/orders", middleware.RequireCurrentBrand(), mediaSupplyHandler.CreateOrder)