mirror of
https://github.com/truewhile/MeBox.git
synced 2026-09-30 19:46:38 +08:00
feat: persist pinned libraries per user with cross-device sync (#20)
Store pinned library IDs on the user record and expose GET/PUT /me/pinned-libraries endpoints. The web client now loads and saves pins through the API, migrates legacy localStorage data once, and applies pinned ordering on both the libraries and home pages. Co-authored-by: Cursor Agent <cursoragent@cursor.com> Co-authored-by: truewhile <truewhile@users.noreply.github.com>
This commit is contained in:
@@ -0,0 +1,49 @@
|
||||
package handler
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
|
||||
"github.com/truewhile/MeBox/internal/middleware"
|
||||
"github.com/truewhile/MeBox/internal/service"
|
||||
)
|
||||
|
||||
type pinnedLibrariesReq struct {
|
||||
LibraryIDs []string `json:"library_ids"`
|
||||
}
|
||||
|
||||
func getPinnedLibrariesHandler(svc *service.Container) gin.HandlerFunc {
|
||||
return func(c *gin.Context) {
|
||||
uid, _ := c.Get(middleware.CtxUserID)
|
||||
ids, err := svc.Profile.GetPinnedLibraryIDs(c.Request.Context(), uid.(string))
|
||||
if err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
if ids == nil {
|
||||
ids = []string{}
|
||||
}
|
||||
c.JSON(http.StatusOK, gin.H{"library_ids": ids})
|
||||
}
|
||||
}
|
||||
|
||||
func setPinnedLibrariesHandler(svc *service.Container) gin.HandlerFunc {
|
||||
return func(c *gin.Context) {
|
||||
var req pinnedLibrariesReq
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
uid, _ := c.Get(middleware.CtxUserID)
|
||||
ids, err := svc.Profile.SetPinnedLibraryIDs(c.Request.Context(), uid.(string), req.LibraryIDs)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
if ids == nil {
|
||||
ids = []string{}
|
||||
}
|
||||
c.JSON(http.StatusOK, gin.H{"library_ids": ids})
|
||||
}
|
||||
}
|
||||
@@ -10,6 +10,8 @@ import (
|
||||
func registerAuthedUserAndLicenseRoutes(authed *gin.RouterGroup, svc *service.Container) {
|
||||
authed.GET("/me", meHandler(svc))
|
||||
authed.PATCH("/me", updateProfileHandler(svc))
|
||||
authed.GET("/me/pinned-libraries", getPinnedLibrariesHandler(svc))
|
||||
authed.PUT("/me/pinned-libraries", setPinnedLibrariesHandler(svc))
|
||||
authed.POST("/me/password", changePasswordHandler(svc))
|
||||
authed.POST("/me/logout", logoutHandler(svc))
|
||||
|
||||
|
||||
@@ -25,6 +25,8 @@ func TestAuthenticatedRouteSurfacesAreRegistered(t *testing.T) {
|
||||
|
||||
for _, want := range []string{
|
||||
"GET /api/me",
|
||||
"GET /api/me/pinned-libraries",
|
||||
"PUT /api/me/pinned-libraries",
|
||||
"GET /api/auth/permissions",
|
||||
"GET /api/libraries",
|
||||
"GET /api/media",
|
||||
|
||||
Reference in New Issue
Block a user