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:
@@ -89,6 +89,105 @@ func (h *MediaSupplyHandler) ListResourcesByIDs(c *gin.Context) {
|
||||
response.Success(c, data)
|
||||
}
|
||||
|
||||
func (h *MediaSupplyHandler) ListFavoriteGroups(c *gin.Context) {
|
||||
data, err := h.svc.ListFavoriteGroups(c.Request.Context())
|
||||
if err != nil {
|
||||
response.Error(c, err)
|
||||
return
|
||||
}
|
||||
response.Success(c, data)
|
||||
}
|
||||
|
||||
func (h *MediaSupplyHandler) ListFavoriteResources(c *gin.Context) {
|
||||
page, err := parseOptionalIntQuery(c, "page")
|
||||
if err != nil {
|
||||
response.Error(c, response.ErrBadRequest(40001, "invalid_page", "分页参数必须是数字"))
|
||||
return
|
||||
}
|
||||
data, err := h.svc.ListFavoriteResources(
|
||||
c.Request.Context(),
|
||||
app.ListMediaSupplyFavoriteResourcesRequest{
|
||||
GroupID: c.Param("group_id"),
|
||||
Keyword: c.Query("keyword"),
|
||||
Page: page,
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
response.Error(c, err)
|
||||
return
|
||||
}
|
||||
response.Success(c, data)
|
||||
}
|
||||
|
||||
func (h *MediaSupplyHandler) CreateFavoriteGroup(c *gin.Context) {
|
||||
var req app.CreateMediaSupplyFavoriteGroupRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
response.Error(c, response.ErrBadRequest(40001, "invalid_params", "常发媒体分组参数不正确"))
|
||||
return
|
||||
}
|
||||
data, err := h.svc.CreateFavoriteGroup(c.Request.Context(), req)
|
||||
if err != nil {
|
||||
response.Error(c, err)
|
||||
return
|
||||
}
|
||||
response.SuccessWithStatus(c, 201, data)
|
||||
}
|
||||
|
||||
func (h *MediaSupplyHandler) DeleteFavoriteGroup(c *gin.Context) {
|
||||
data, err := h.svc.DeleteFavoriteGroup(c.Request.Context(), c.Param("group_id"))
|
||||
if err != nil {
|
||||
response.Error(c, err)
|
||||
return
|
||||
}
|
||||
response.Success(c, data)
|
||||
}
|
||||
|
||||
func (h *MediaSupplyHandler) PutFavoriteResource(c *gin.Context) {
|
||||
resourceID, err := strconv.ParseInt(c.Param("resource_id"), 10, 64)
|
||||
if err != nil || resourceID <= 0 {
|
||||
response.Error(c, response.ErrBadRequest(40070, "invalid_media_supply_favorite_resource", "常发媒体资源参数不正确"))
|
||||
return
|
||||
}
|
||||
data, err := h.svc.PutFavoriteResource(c.Request.Context(), c.Param("group_id"), resourceID)
|
||||
if err != nil {
|
||||
response.Error(c, err)
|
||||
return
|
||||
}
|
||||
response.Success(c, data)
|
||||
}
|
||||
|
||||
func (h *MediaSupplyHandler) DeleteFavoriteResourceFromGroup(c *gin.Context) {
|
||||
resourceID, err := strconv.ParseInt(c.Param("resource_id"), 10, 64)
|
||||
if err != nil || resourceID <= 0 {
|
||||
response.Error(c, response.ErrBadRequest(40070, "invalid_media_supply_favorite_resource", "常发媒体资源参数不正确"))
|
||||
return
|
||||
}
|
||||
data, err := h.svc.DeleteFavoriteResourceFromGroup(
|
||||
c.Request.Context(),
|
||||
c.Param("group_id"),
|
||||
resourceID,
|
||||
)
|
||||
if err != nil {
|
||||
response.Error(c, err)
|
||||
return
|
||||
}
|
||||
response.Success(c, data)
|
||||
}
|
||||
|
||||
func (h *MediaSupplyHandler) DeleteFavoriteResource(c *gin.Context) {
|
||||
resourceID, err := strconv.ParseInt(c.Param("resource_id"), 10, 64)
|
||||
if err != nil || resourceID <= 0 {
|
||||
response.Error(c, response.ErrBadRequest(40070, "invalid_media_supply_favorite_resource", "常发媒体资源参数不正确"))
|
||||
return
|
||||
}
|
||||
data, err := h.svc.DeleteFavoriteResource(c.Request.Context(), resourceID)
|
||||
if err != nil {
|
||||
response.Error(c, err)
|
||||
return
|
||||
}
|
||||
response.Success(c, data)
|
||||
}
|
||||
|
||||
func (h *MediaSupplyHandler) QueueSync(c *gin.Context) {
|
||||
response.Error(c, response.ErrNotFound(40400, "not_found", "接口不存在"))
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user