package handlers import ( "errors" "net/http" "sort" "strings" "github.com/gin-gonic/gin" "sproutgate-backend/internal/auth" "sproutgate-backend/internal/models" "sproutgate-backend/internal/storage" ) // ListPublicUsers 公开用户目录(未封禁用户,默认按注册时间从早到晚)。 func (h *Handler) ListPublicUsers(c *gin.Context) { users, err := h.store.ListUsers() if err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to load users"}) return } out := make([]models.PublicUserListEntry, 0, len(users)) for _, u := range users { if u.Banned { continue } out = append(out, u.PublicListEntry()) } sort.Slice(out, func(i, j int) bool { return strings.TrimSpace(out[i].CreatedAt) < strings.TrimSpace(out[j].CreatedAt) }) c.JSON(http.StatusOK, gin.H{"total": len(out), "users": out}) } func (h *Handler) GetPublicUser(c *gin.Context) { account := strings.TrimSpace(c.Param("account")) if account == "" { c.JSON(http.StatusBadRequest, gin.H{"error": "account is required"}) return } users, err := h.store.ListUsers() if err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to load users"}) return } for _, user := range users { if strings.EqualFold(strings.TrimSpace(user.Account), account) { if user.Banned { c.JSON(http.StatusNotFound, gin.H{"error": "user not found"}) return } likeCount, err := h.store.CountProfileLikes(user.Account) if err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to load likes"}) return } payload := gin.H{ "user": user.PublicProfile(), "profileLikeCount": likeCount, } if token := bearerToken(c.GetHeader("Authorization")); token != "" { claims, err := auth.ParseToken(h.store.JWTSecret(), h.store.JWTIssuer(), token) if err == nil { viewer, vFound, vErr := h.store.GetUser(claims.Account) if vErr == nil && vFound && !viewer.Banned && claims.TokenEpoch == viewer.TokenEpoch { if strings.EqualFold(viewer.Account, user.Account) { payload["viewerIsOwner"] = true } else { has, _ := h.store.ViewerHasLikedProfileToday(viewer.Account, user.Account) payload["viewerHasLikedToday"] = has rem, _ := h.store.ProfileLikeRemainingToday(viewer.Account) payload["viewerLikesRemainingToday"] = rem payload["profileLikeDailyMax"] = storage.MaxProfileLikesPerDay } } } } c.JSON(http.StatusOK, payload) return } } c.JSON(http.StatusNotFound, gin.H{"error": "user not found"}) } func (h *Handler) PostPublicProfileLike(c *gin.Context) { likedParam := strings.TrimSpace(c.Param("account")) if likedParam == "" { c.JSON(http.StatusBadRequest, gin.H{"error": "account is required"}) return } token := bearerToken(c.GetHeader("Authorization")) if token == "" { c.JSON(http.StatusUnauthorized, gin.H{"error": "missing token"}) return } claims, err := auth.ParseToken(h.store.JWTSecret(), h.store.JWTIssuer(), token) if err != nil { c.JSON(http.StatusUnauthorized, gin.H{"error": "invalid token"}) return } likerUser, found, err := h.store.GetUser(claims.Account) if err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to load user"}) return } if !found { c.JSON(http.StatusUnauthorized, gin.H{"error": "user not found"}) return } if abortIfUserBanned(c, likerUser) { return } if abortIfTokenEpochStale(c, claims, likerUser) { return } newCount, err := h.store.AddProfileLike(likerUser.Account, likedParam) if err != nil { switch { case errors.Is(err, storage.ErrCannotLikeOwnProfile): c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) return case errors.Is(err, storage.ErrProfileLikeTarget): c.JSON(http.StatusNotFound, gin.H{"error": err.Error()}) return case errors.Is(err, storage.ErrAlreadyLikedToday): c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) return case errors.Is(err, storage.ErrDailyLikeLimitReached): rem, _ := h.store.ProfileLikeRemainingToday(likerUser.Account) c.JSON(http.StatusBadRequest, gin.H{ "error": err.Error(), "viewerLikesRemainingToday": rem, "profileLikeDailyMax": storage.MaxProfileLikesPerDay, }) return case errors.Is(err, storage.ErrProfileLikeLiker): c.JSON(http.StatusForbidden, gin.H{"error": err.Error()}) return default: c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) return } } rem, _ := h.store.ProfileLikeRemainingToday(likerUser.Account) c.JSON(http.StatusOK, gin.H{ "profileLikeCount": newCount, "viewerHasLikedToday": true, "viewerLikesRemainingToday": rem, "profileLikeDailyMax": storage.MaxProfileLikesPerDay, }) }