Files
SproutGate/sproutgate-backend/internal/handlers/public_profile.go

155 lines
4.7 KiB
Go

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,
})
}