mirror of
https://github.com/snowykami/neo-blog.git
synced 2025-09-26 11:06:23 +00:00
feat: 添加评论功能的客户端信息显示选项,更新相关接口和组件
This commit is contained in:
@ -1,47 +1,48 @@
|
||||
package dto
|
||||
|
||||
type CommentDto struct {
|
||||
ID uint `json:"id"`
|
||||
TargetID uint `json:"target_id"`
|
||||
TargetType string `json:"target_type"` // 目标类型,如 "post", "page"
|
||||
Content string `json:"content"`
|
||||
ReplyID uint `json:"reply_id"` // 回复的评论ID
|
||||
Depth int `json:"depth"` // 评论的层级深度
|
||||
CreatedAt string `json:"created_at"`
|
||||
UpdatedAt string `json:"updated_at"`
|
||||
User UserDto `json:"user"` // 评论的
|
||||
ReplyCount uint64 `json:"reply_count"` // 回复数量
|
||||
LikeCount uint64 `json:"like_count"` // 点赞数量
|
||||
IsLiked bool `json:"is_liked"` // 当前用户是否点赞
|
||||
IsPrivate bool `json:"is_private"`
|
||||
Location string `json:"location"` // 用户位置,基于IP
|
||||
OS string `json:"os"` // 用户操作系统,基于User-Agent
|
||||
Browser string `json:"browser"` // 用户浏览器,基于User-Agent
|
||||
ID uint `json:"id"`
|
||||
TargetID uint `json:"target_id"`
|
||||
TargetType string `json:"target_type"` // 目标类型,如 "post", "page"
|
||||
Content string `json:"content"`
|
||||
ReplyID uint `json:"reply_id"` // 回复的评论ID
|
||||
Depth int `json:"depth"` // 评论的层级深度
|
||||
CreatedAt string `json:"created_at"`
|
||||
UpdatedAt string `json:"updated_at"`
|
||||
User UserDto `json:"user"` // 评论的
|
||||
ReplyCount uint64 `json:"reply_count"` // 回复数量
|
||||
LikeCount uint64 `json:"like_count"` // 点赞数量
|
||||
IsLiked bool `json:"is_liked"` // 当前用户是否点赞
|
||||
IsPrivate bool `json:"is_private"`
|
||||
Location string `json:"location"` // 用户位置,基于IP
|
||||
OS string `json:"os"` // 用户操作系统,基于User-Agent
|
||||
Browser string `json:"browser"` // 用户浏览器,基于User-Agent
|
||||
}
|
||||
|
||||
type CreateCommentReq struct {
|
||||
TargetID uint `json:"target_id" binding:"required"` // 目标ID
|
||||
TargetType string `json:"target_type" binding:"required"` // 目标类型,如 "post", "page"
|
||||
Content string `json:"content" binding:"required"` // 评论内容
|
||||
ReplyID uint `json:"reply_id"` // 回复的评论ID
|
||||
IsPrivate bool `json:"is_private"` // 是否私密评论,默认false
|
||||
RemoteAddr string `json:"remote_addr"` // 远程地址
|
||||
UserAgent string `json:"user_agent"` // 用户代理
|
||||
TargetID uint `json:"target_id" binding:"required"` // 目标ID
|
||||
TargetType string `json:"target_type" binding:"required"` // 目标类型,如 "post", "page"
|
||||
Content string `json:"content" binding:"required"` // 评论内容
|
||||
ReplyID uint `json:"reply_id"` // 回复的评论ID
|
||||
IsPrivate bool `json:"is_private"` // 是否私密评论,默认false
|
||||
RemoteAddr string `json:"remote_addr"` // 远程地址
|
||||
UserAgent string `json:"user_agent"` // 用户代理
|
||||
ShowClientInfo bool `json:"show_client_info"` // 是否显示客户端信息
|
||||
}
|
||||
|
||||
type UpdateCommentReq struct {
|
||||
CommentID uint `json:"comment_id" binding:"required"` // 评论ID
|
||||
Content string `json:"content" binding:"required"` // 评论内容
|
||||
IsPrivate bool `json:"is_private"` // 是否私密
|
||||
CommentID uint `json:"comment_id" binding:"required"` // 评论ID
|
||||
Content string `json:"content" binding:"required"` // 评论内容
|
||||
IsPrivate bool `json:"is_private"` // 是否私密
|
||||
}
|
||||
|
||||
type GetCommentListReq struct {
|
||||
TargetID uint `json:"target_id" binding:"required"`
|
||||
TargetType string `json:"target_type" binding:"required"`
|
||||
CommentID uint `json:"comment_id"` // 获取某条评论的所有子评论
|
||||
OrderBy string `json:"order_by"` // 排序方式
|
||||
Page uint64 `json:"page"` // 页码
|
||||
Size uint64 `json:"size"`
|
||||
Desc bool `json:"desc"`
|
||||
Depth int `json:"depth"` // 评论的层级深度
|
||||
TargetID uint `json:"target_id" binding:"required"`
|
||||
TargetType string `json:"target_type" binding:"required"`
|
||||
CommentID uint `json:"comment_id"` // 获取某条评论的所有子评论
|
||||
OrderBy string `json:"order_by"` // 排序方式
|
||||
Page uint64 `json:"page"` // 页码
|
||||
Size uint64 `json:"size"`
|
||||
Desc bool `json:"desc"`
|
||||
Depth int `json:"depth"` // 评论的层级深度
|
||||
}
|
||||
|
@ -7,19 +7,20 @@ import (
|
||||
|
||||
type Comment struct {
|
||||
gorm.Model
|
||||
UserID uint `gorm:"index"` // 评论的用户ID
|
||||
User User `gorm:"foreignKey:UserID;references:ID"` // 关联的用户
|
||||
TargetID uint `gorm:"index"` // 目标ID
|
||||
TargetType string `gorm:"index"` // 目标类型,如 "post", "page"
|
||||
ReplyID uint `gorm:"index"` // 回复的评论ID
|
||||
Content string `gorm:"type:text"` // 评论内容
|
||||
Depth int `gorm:"default:0"` // 评论的层级深度,从0开始计数
|
||||
IsPrivate bool `gorm:"default:false"` // 是否为私密评论,私密评论只有评论者和被评论对象所有者可见
|
||||
RemoteAddr string `gorm:"type:text"` // 远程地址
|
||||
UserAgent string `gorm:"type:text"`
|
||||
Location string `gorm:"type:text"` // 用户位置,基于IP
|
||||
LikeCount uint64
|
||||
CommentCount uint64
|
||||
UserID uint `gorm:"index"` // 评论的用户ID
|
||||
User User `gorm:"foreignKey:UserID;references:ID"` // 关联的用户
|
||||
TargetID uint `gorm:"index"` // 目标ID
|
||||
TargetType string `gorm:"index"` // 目标类型,如 "post", "page"
|
||||
ReplyID uint `gorm:"index"` // 回复的评论ID
|
||||
Content string `gorm:"type:text"` // 评论内容
|
||||
Depth int `gorm:"default:0"` // 评论的层级深度,从0开始计数
|
||||
IsPrivate bool `gorm:"default:false"` // 是否为私密评论,私密评论只有评论者和被评论对象所有者可见
|
||||
RemoteAddr string `gorm:"type:text"` // 远程地址
|
||||
UserAgent string `gorm:"type:text"`
|
||||
Location string `gorm:"type:text"` // 用户位置,基于IP
|
||||
LikeCount uint64
|
||||
CommentCount uint64
|
||||
ShowClientInfo bool `gorm:"default:false"` // 是否显示客户端信息
|
||||
}
|
||||
|
||||
func (c *Comment) AfterCreate(tx *gorm.DB) (err error) {
|
||||
|
@ -121,6 +121,7 @@ func (cr *CommentRepo) CreateComment(comment *model.Comment) (uint, error) {
|
||||
})
|
||||
return commentID, err
|
||||
}
|
||||
|
||||
func (cr *CommentRepo) UpdateComment(comment *model.Comment) error {
|
||||
if comment.ID == 0 {
|
||||
return errs.New(http.StatusBadRequest, "invalid comment ID", nil)
|
||||
|
@ -1,184 +1,213 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"context"
|
||||
"strconv"
|
||||
"context"
|
||||
"strconv"
|
||||
|
||||
"github.com/snowykami/neo-blog/pkg/constant"
|
||||
"github.com/snowykami/neo-blog/pkg/utils"
|
||||
"github.com/snowykami/neo-blog/pkg/constant"
|
||||
"github.com/snowykami/neo-blog/pkg/utils"
|
||||
|
||||
"github.com/snowykami/neo-blog/internal/ctxutils"
|
||||
"github.com/snowykami/neo-blog/internal/dto"
|
||||
"github.com/snowykami/neo-blog/internal/model"
|
||||
"github.com/snowykami/neo-blog/internal/repo"
|
||||
"github.com/snowykami/neo-blog/pkg/errs"
|
||||
"github.com/snowykami/neo-blog/internal/ctxutils"
|
||||
"github.com/snowykami/neo-blog/internal/dto"
|
||||
"github.com/snowykami/neo-blog/internal/model"
|
||||
"github.com/snowykami/neo-blog/internal/repo"
|
||||
"github.com/snowykami/neo-blog/pkg/errs"
|
||||
)
|
||||
|
||||
type CommentService struct{}
|
||||
|
||||
func NewCommentService() *CommentService {
|
||||
return &CommentService{}
|
||||
return &CommentService{}
|
||||
}
|
||||
|
||||
func (cs *CommentService) CreateComment(ctx context.Context, req *dto.CreateCommentReq) (uint, error) {
|
||||
currentUser, ok := ctxutils.GetCurrentUser(ctx)
|
||||
if !ok {
|
||||
return 0, errs.ErrUnauthorized
|
||||
}
|
||||
currentUser, ok := ctxutils.GetCurrentUser(ctx)
|
||||
if !ok {
|
||||
return 0, errs.ErrUnauthorized
|
||||
}
|
||||
|
||||
if ok, err := cs.checkTargetExists(req.TargetID, req.TargetType); !ok {
|
||||
if err != nil {
|
||||
return 0, errs.New(errs.ErrBadRequest.Code, "target not found", err)
|
||||
}
|
||||
return 0, errs.ErrBadRequest
|
||||
}
|
||||
if ok, err := cs.checkTargetExists(req.TargetID, req.TargetType); !ok {
|
||||
if err != nil {
|
||||
return 0, errs.New(errs.ErrBadRequest.Code, "target not found", err)
|
||||
}
|
||||
return 0, errs.ErrBadRequest
|
||||
}
|
||||
|
||||
comment := &model.Comment{
|
||||
Content: req.Content,
|
||||
ReplyID: req.ReplyID,
|
||||
TargetID: req.TargetID,
|
||||
TargetType: req.TargetType,
|
||||
UserID: currentUser.ID,
|
||||
IsPrivate: req.IsPrivate,
|
||||
RemoteAddr: req.RemoteAddr,
|
||||
UserAgent: req.UserAgent,
|
||||
}
|
||||
comment := &model.Comment{
|
||||
Content: req.Content,
|
||||
ReplyID: req.ReplyID,
|
||||
TargetID: req.TargetID,
|
||||
TargetType: req.TargetType,
|
||||
UserID: currentUser.ID,
|
||||
IsPrivate: req.IsPrivate,
|
||||
RemoteAddr: req.RemoteAddr,
|
||||
UserAgent: req.UserAgent,
|
||||
ShowClientInfo: req.ShowClientInfo,
|
||||
}
|
||||
|
||||
commentID, err := repo.Comment.CreateComment(comment)
|
||||
commentID, err := repo.Comment.CreateComment(comment)
|
||||
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
return commentID, nil
|
||||
return commentID, nil
|
||||
}
|
||||
|
||||
func (cs *CommentService) UpdateComment(ctx context.Context, req *dto.UpdateCommentReq) error {
|
||||
currentUser, ok := ctxutils.GetCurrentUser(ctx)
|
||||
if !ok {
|
||||
return errs.ErrUnauthorized
|
||||
}
|
||||
currentUser, ok := ctxutils.GetCurrentUser(ctx)
|
||||
if !ok {
|
||||
return errs.ErrUnauthorized
|
||||
}
|
||||
|
||||
comment, err := repo.Comment.GetComment(strconv.Itoa(int(req.CommentID)))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
comment, err := repo.Comment.GetComment(strconv.Itoa(int(req.CommentID)))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if currentUser.ID != comment.UserID {
|
||||
return errs.ErrForbidden
|
||||
}
|
||||
if currentUser.ID != comment.UserID {
|
||||
return errs.ErrForbidden
|
||||
}
|
||||
|
||||
comment.Content = req.Content
|
||||
comment.IsPrivate = req.IsPrivate
|
||||
comment.Content = req.Content
|
||||
comment.IsPrivate = req.IsPrivate
|
||||
|
||||
err = repo.Comment.UpdateComment(comment)
|
||||
err = repo.Comment.UpdateComment(comment)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
return nil
|
||||
}
|
||||
|
||||
func (cs *CommentService) DeleteComment(ctx context.Context, commentID string) error {
|
||||
currentUser, ok := ctxutils.GetCurrentUser(ctx)
|
||||
if !ok {
|
||||
return errs.ErrUnauthorized
|
||||
}
|
||||
if commentID == "" {
|
||||
return errs.ErrBadRequest
|
||||
}
|
||||
currentUser, ok := ctxutils.GetCurrentUser(ctx)
|
||||
if !ok {
|
||||
return errs.ErrUnauthorized
|
||||
}
|
||||
if commentID == "" {
|
||||
return errs.ErrBadRequest
|
||||
}
|
||||
|
||||
comment, err := repo.Comment.GetComment(commentID)
|
||||
if err != nil {
|
||||
return errs.New(errs.ErrNotFound.Code, "comment not found", err)
|
||||
}
|
||||
comment, err := repo.Comment.GetComment(commentID)
|
||||
if err != nil {
|
||||
return errs.New(errs.ErrNotFound.Code, "comment not found", err)
|
||||
}
|
||||
|
||||
if comment.UserID != currentUser.ID {
|
||||
return errs.ErrForbidden
|
||||
}
|
||||
if comment.UserID != currentUser.ID {
|
||||
return errs.ErrForbidden
|
||||
}
|
||||
|
||||
if err := repo.Comment.DeleteComment(commentID); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := repo.Comment.DeleteComment(commentID); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
return nil
|
||||
}
|
||||
|
||||
func (cs *CommentService) GetComment(ctx context.Context, commentID string) (*dto.CommentDto, error) {
|
||||
comment, err := repo.Comment.GetComment(commentID)
|
||||
comment, err := repo.Comment.GetComment(commentID)
|
||||
|
||||
if err != nil {
|
||||
return nil, errs.New(errs.ErrNotFound.Code, "comment not found", err)
|
||||
}
|
||||
if err != nil {
|
||||
return nil, errs.New(errs.ErrNotFound.Code, "comment not found", err)
|
||||
}
|
||||
|
||||
commentDto := dto.CommentDto{
|
||||
ID: comment.ID,
|
||||
TargetID: comment.TargetID,
|
||||
TargetType: comment.TargetType,
|
||||
Content: comment.Content,
|
||||
ReplyID: comment.ReplyID,
|
||||
Depth: comment.Depth,
|
||||
CreatedAt: comment.CreatedAt.String(),
|
||||
UpdatedAt: comment.UpdatedAt.String(),
|
||||
User: comment.User.ToDto(),
|
||||
}
|
||||
// TODO: 返回更多字段
|
||||
currentUserID := uint(0)
|
||||
if currentUser, ok := ctxutils.GetCurrentUser(ctx); ok {
|
||||
currentUserID = currentUser.ID
|
||||
}
|
||||
if comment.IsPrivate && currentUserID != comment.UserID {
|
||||
return nil, errs.ErrForbidden
|
||||
}
|
||||
isLiked := false
|
||||
if currentUserID != 0 {
|
||||
isLiked, _ = repo.Like.IsLiked(currentUserID, comment.ID, constant.TargetTypeComment)
|
||||
}
|
||||
ua := utils.ParseUA(comment.UserAgent)
|
||||
if !comment.ShowClientInfo {
|
||||
comment.Location = ""
|
||||
ua.OS = ""
|
||||
ua.OSVersion = ""
|
||||
ua.Browser = ""
|
||||
ua.BrowserVer = ""
|
||||
}
|
||||
|
||||
return &commentDto, err
|
||||
commentDto := dto.CommentDto{
|
||||
ID: comment.ID,
|
||||
Content: comment.Content,
|
||||
TargetID: comment.TargetID,
|
||||
TargetType: comment.TargetType,
|
||||
ReplyID: comment.ReplyID,
|
||||
CreatedAt: comment.CreatedAt.String(),
|
||||
UpdatedAt: comment.UpdatedAt.String(),
|
||||
Depth: comment.Depth,
|
||||
User: comment.User.ToDto(),
|
||||
ReplyCount: comment.CommentCount,
|
||||
LikeCount: comment.LikeCount,
|
||||
IsLiked: isLiked,
|
||||
IsPrivate: comment.IsPrivate,
|
||||
OS: ua.OS + " " + ua.OSVersion,
|
||||
Browser: ua.Browser + " " + ua.BrowserVer,
|
||||
Location: comment.Location,
|
||||
}
|
||||
|
||||
return &commentDto, err
|
||||
}
|
||||
|
||||
func (cs *CommentService) GetCommentList(ctx context.Context, req *dto.GetCommentListReq) ([]dto.CommentDto, error) {
|
||||
currentUserID := uint(0)
|
||||
if currentUser, ok := ctxutils.GetCurrentUser(ctx); ok {
|
||||
currentUserID = currentUser.ID
|
||||
}
|
||||
|
||||
comments, err := repo.Comment.ListComments(currentUserID, req.TargetID, req.CommentID, req.TargetType, req.Page, req.Size, req.OrderBy, req.Desc, req.Depth)
|
||||
if err != nil {
|
||||
return nil, errs.New(errs.ErrInternalServer.Code, "failed to list comments", err)
|
||||
}
|
||||
|
||||
commentDtos := make([]dto.CommentDto, 0)
|
||||
|
||||
for _, comment := range comments {
|
||||
//replyCount, _ := repo.Comment.CountReplyComments(currentUserID, comment.ID)
|
||||
isLiked := false
|
||||
if currentUserID != 0 {
|
||||
isLiked, _ = repo.Like.IsLiked(currentUserID, comment.ID, constant.TargetTypeComment)
|
||||
}
|
||||
ua := utils.ParseUA(comment.UserAgent)
|
||||
commentDto := dto.CommentDto{
|
||||
ID: comment.ID,
|
||||
Content: comment.Content,
|
||||
TargetID: comment.TargetID,
|
||||
TargetType: comment.TargetType,
|
||||
ReplyID: comment.ReplyID,
|
||||
CreatedAt: comment.CreatedAt.String(),
|
||||
UpdatedAt: comment.UpdatedAt.String(),
|
||||
Depth: comment.Depth,
|
||||
User: comment.User.ToDto(),
|
||||
ReplyCount: comment.CommentCount,
|
||||
LikeCount: comment.LikeCount,
|
||||
IsLiked: isLiked,
|
||||
IsPrivate: comment.IsPrivate,
|
||||
OS: ua.OS + " " + ua.OSVersion,
|
||||
Browser: ua.Browser + " " + ua.BrowserVer,
|
||||
Location: comment.Location,
|
||||
}
|
||||
commentDtos = append(commentDtos, commentDto)
|
||||
}
|
||||
return commentDtos, nil
|
||||
currentUserID := uint(0)
|
||||
if currentUser, ok := ctxutils.GetCurrentUser(ctx); ok {
|
||||
currentUserID = currentUser.ID
|
||||
}
|
||||
comments, err := repo.Comment.ListComments(currentUserID, req.TargetID, req.CommentID, req.TargetType, req.Page, req.Size, req.OrderBy, req.Desc, req.Depth)
|
||||
if err != nil {
|
||||
return nil, errs.New(errs.ErrInternalServer.Code, "failed to list comments", err)
|
||||
}
|
||||
commentDtos := make([]dto.CommentDto, 0)
|
||||
for _, comment := range comments {
|
||||
//replyCount, _ := repo.Comment.CountReplyComments(currentUserID, comment.ID)
|
||||
isLiked := false
|
||||
if currentUserID != 0 {
|
||||
isLiked, _ = repo.Like.IsLiked(currentUserID, comment.ID, constant.TargetTypeComment)
|
||||
}
|
||||
ua := utils.ParseUA(comment.UserAgent)
|
||||
commentDto := dto.CommentDto{
|
||||
ID: comment.ID,
|
||||
Content: comment.Content,
|
||||
TargetID: comment.TargetID,
|
||||
TargetType: comment.TargetType,
|
||||
ReplyID: comment.ReplyID,
|
||||
CreatedAt: comment.CreatedAt.String(),
|
||||
UpdatedAt: comment.UpdatedAt.String(),
|
||||
Depth: comment.Depth,
|
||||
User: comment.User.ToDto(),
|
||||
ReplyCount: comment.CommentCount,
|
||||
LikeCount: comment.LikeCount,
|
||||
IsLiked: isLiked,
|
||||
IsPrivate: comment.IsPrivate,
|
||||
OS: ua.OS + " " + ua.OSVersion,
|
||||
Browser: ua.Browser + " " + ua.BrowserVer,
|
||||
Location: comment.Location,
|
||||
}
|
||||
if !comment.ShowClientInfo {
|
||||
commentDto.Location = ""
|
||||
commentDto.OS = ""
|
||||
commentDto.Browser = ""
|
||||
}
|
||||
commentDtos = append(commentDtos, commentDto)
|
||||
}
|
||||
return commentDtos, nil
|
||||
}
|
||||
|
||||
func (cs *CommentService) checkTargetExists(targetID uint, targetType string) (bool, error) {
|
||||
switch targetType {
|
||||
case constant.TargetTypePost:
|
||||
if _, err := repo.Post.GetPostByID(strconv.Itoa(int(targetID))); err != nil {
|
||||
return false, errs.New(errs.ErrNotFound.Code, "post not found", err)
|
||||
}
|
||||
default:
|
||||
return false, errs.New(errs.ErrBadRequest.Code, "invalid target type", nil)
|
||||
}
|
||||
return true, nil
|
||||
switch targetType {
|
||||
case constant.TargetTypePost:
|
||||
if _, err := repo.Post.GetPostByID(strconv.Itoa(int(targetID))); err != nil {
|
||||
return false, errs.New(errs.ErrNotFound.Code, "post not found", err)
|
||||
}
|
||||
default:
|
||||
return false, errs.New(errs.ErrBadRequest.Code, "invalid target type", nil)
|
||||
}
|
||||
return true, nil
|
||||
}
|
||||
|
@ -1,7 +1,7 @@
|
||||
package utils
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
type oidcUtils struct{}
|
||||
@ -10,59 +10,59 @@ var Oidc = oidcUtils{}
|
||||
|
||||
// RequestToken 请求访问令牌
|
||||
func (u *oidcUtils) RequestToken(tokenEndpoint, clientID, clientSecret, code, redirectURI string) (*TokenResponse, error) {
|
||||
tokenResp, err := client.R().
|
||||
SetFormData(map[string]string{
|
||||
"grant_type": "authorization_code",
|
||||
"client_id": clientID,
|
||||
"client_secret": clientSecret,
|
||||
"code": code,
|
||||
"redirect_uri": redirectURI,
|
||||
}).
|
||||
SetHeader("Accept", "application/json").
|
||||
SetResult(&TokenResponse{}).
|
||||
Post(tokenEndpoint)
|
||||
tokenResp, err := client.R().
|
||||
SetFormData(map[string]string{
|
||||
"grant_type": "authorization_code",
|
||||
"client_id": clientID,
|
||||
"client_secret": clientSecret,
|
||||
"code": code,
|
||||
"redirect_uri": redirectURI,
|
||||
}).
|
||||
SetHeader("Accept", "application/json").
|
||||
SetResult(&TokenResponse{}).
|
||||
Post(tokenEndpoint)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if tokenResp.StatusCode() != 200 {
|
||||
return nil, fmt.Errorf("状态码: %d,响应: %s", tokenResp.StatusCode(), tokenResp.String())
|
||||
}
|
||||
return tokenResp.Result().(*TokenResponse), nil
|
||||
if tokenResp.StatusCode() != 200 {
|
||||
return nil, fmt.Errorf("状态码: %d,响应: %s", tokenResp.StatusCode(), tokenResp.String())
|
||||
}
|
||||
return tokenResp.Result().(*TokenResponse), nil
|
||||
}
|
||||
|
||||
// RequestUserInfo 请求用户信息
|
||||
func (u *oidcUtils) RequestUserInfo(userInfoEndpoint, accessToken string) (*UserInfo, error) {
|
||||
userInfoResp, err := client.R().
|
||||
SetHeader("Authorization", "Bearer "+accessToken).
|
||||
SetHeader("Accept", "application/json").
|
||||
SetResult(&UserInfo{}).
|
||||
Get(userInfoEndpoint)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
userInfoResp, err := client.R().
|
||||
SetHeader("Authorization", "Bearer "+accessToken).
|
||||
SetHeader("Accept", "application/json").
|
||||
SetResult(&UserInfo{}).
|
||||
Get(userInfoEndpoint)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if userInfoResp.StatusCode() != 200 {
|
||||
return nil, fmt.Errorf("状态码: %d,响应: %s", userInfoResp.StatusCode(), userInfoResp.String())
|
||||
}
|
||||
if userInfoResp.StatusCode() != 200 {
|
||||
return nil, fmt.Errorf("状态码: %d,响应: %s", userInfoResp.StatusCode(), userInfoResp.String())
|
||||
}
|
||||
|
||||
return userInfoResp.Result().(*UserInfo), nil
|
||||
return userInfoResp.Result().(*UserInfo), nil
|
||||
}
|
||||
|
||||
type TokenResponse struct {
|
||||
AccessToken string `json:"access_token"`
|
||||
TokenType string `json:"token_type"`
|
||||
ExpiresIn int `json:"expires_in"`
|
||||
IDToken string `json:"id_token,omitempty"`
|
||||
RefreshToken string `json:"refresh_token,omitempty"`
|
||||
AccessToken string `json:"access_token"`
|
||||
TokenType string `json:"token_type"`
|
||||
ExpiresIn int `json:"expires_in"`
|
||||
IDToken string `json:"id_token,omitempty"`
|
||||
RefreshToken string `json:"refresh_token,omitempty"`
|
||||
}
|
||||
|
||||
// UserInfo 定义用户信息结构
|
||||
type UserInfo struct {
|
||||
Sub string `json:"sub"`
|
||||
Name string `json:"name"`
|
||||
Email string `json:"email"`
|
||||
Picture string `json:"picture,omitempty"`
|
||||
Groups []string `json:"groups,omitempty"` // 可选字段,OIDC提供的用户组信息
|
||||
Sub string `json:"sub"`
|
||||
Name string `json:"name"`
|
||||
Email string `json:"email"`
|
||||
Picture string `json:"picture,omitempty"`
|
||||
Groups []string `json:"groups,omitempty"` // 可选字段,OIDC提供的用户组信息
|
||||
}
|
||||
|
@ -12,13 +12,15 @@ export async function createComment(
|
||||
targetId,
|
||||
content,
|
||||
replyId = null,
|
||||
isPrivate = false
|
||||
isPrivate = false,
|
||||
showClientInfo = true,
|
||||
}: {
|
||||
targetType: TargetType
|
||||
targetId: number
|
||||
content: string
|
||||
replyId: number | null
|
||||
isPrivate: boolean
|
||||
showClientInfo: boolean
|
||||
}
|
||||
): Promise<BaseResponse<{ id: number }>> {
|
||||
const res = await axiosClient.post<BaseResponse<{ id: number }>>('/comment/c', {
|
||||
@ -26,7 +28,8 @@ export async function createComment(
|
||||
targetId,
|
||||
content,
|
||||
replyId,
|
||||
isPrivate
|
||||
isPrivate,
|
||||
showClientInfo,
|
||||
})
|
||||
return res.data
|
||||
}
|
||||
|
@ -3,7 +3,7 @@ import { User } from "@/models/user";
|
||||
import { useTranslations } from "next-intl";
|
||||
import { useState } from "react";
|
||||
import { toast } from "sonner";
|
||||
import GravatarAvatar, { getGravatarByUser } from "@/components/common/gravatar";
|
||||
import GravatarAvatar from "@/components/common/gravatar";
|
||||
import { CircleUser } from "lucide-react";
|
||||
import { Textarea } from "@/components/ui/textarea";
|
||||
import { Checkbox } from "@/components/ui/checkbox"
|
||||
@ -20,7 +20,7 @@ export function CommentInput(
|
||||
isUpdate = false
|
||||
}: {
|
||||
user: User | null,
|
||||
onCommentSubmitted: ({ commentContent, isPrivate }: { commentContent: string, isPrivate: boolean }) => void,
|
||||
onCommentSubmitted: ({ commentContent, isPrivate, showClientInfo }: { commentContent: string, isPrivate: boolean, showClientInfo: boolean }) => void,
|
||||
initContent?: string,
|
||||
initIsPrivate?: boolean,
|
||||
placeholder?: string,
|
||||
@ -33,6 +33,7 @@ export function CommentInput(
|
||||
const clickToUserProfile = useToUserProfile();
|
||||
|
||||
const [isPrivate, setIsPrivate] = useState(initIsPrivate);
|
||||
const [showClientInfo, setShowClientInfo] = useState(true);
|
||||
const [commentContent, setCommentContent] = useState(initContent);
|
||||
|
||||
const handleCommentSubmit = async () => {
|
||||
@ -54,7 +55,7 @@ export function CommentInput(
|
||||
toast.warning(t("comment_unchanged"));
|
||||
return;
|
||||
}
|
||||
onCommentSubmitted({ commentContent, isPrivate });
|
||||
onCommentSubmitted({ commentContent, isPrivate, showClientInfo });
|
||||
setCommentContent("");
|
||||
};
|
||||
|
||||
@ -62,7 +63,7 @@ export function CommentInput(
|
||||
<div className="fade-in-up">
|
||||
<div className="flex py-4 fade-in">
|
||||
<div onClick={user ? () => clickToUserProfile(user.username) : clickToLogin} className="cursor-pointer flex-shrink-0 w-10 h-10 fade-in">
|
||||
{user && <GravatarAvatar className="w-full h-full" url={user.avatarUrl} email={user.email} size={100}/>}
|
||||
{user && <GravatarAvatar className="w-full h-full" url={user.avatarUrl} email={user.email} size={100} />}
|
||||
{!user && <CircleUser className="w-full h-full fade-in" />}
|
||||
</div>
|
||||
<div className="flex-1 pl-2 fade-in-up">
|
||||
@ -75,6 +76,13 @@ export function CommentInput(
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex justify-end fade-in-up space-x-4 items-center">
|
||||
<div className="flex items-center space-x-2">
|
||||
<Checkbox
|
||||
checked={showClientInfo}
|
||||
onCheckedChange={checked => setShowClientInfo(checked === true)}
|
||||
/>
|
||||
<Label onClick={() => setShowClientInfo(prev => !prev)}>{t("show_client_info")}</Label>
|
||||
</div>
|
||||
<div className="flex items-center space-x-2">
|
||||
<Checkbox
|
||||
checked={isPrivate}
|
||||
|
@ -123,13 +123,14 @@ export function CommentItem(
|
||||
});
|
||||
}
|
||||
|
||||
const onReply = ({ commentContent, isPrivate }: { commentContent: string, isPrivate: boolean }) => {
|
||||
const onReply = ({ commentContent, isPrivate, showClientInfo }: { commentContent: string, isPrivate: boolean, showClientInfo: boolean }) => {
|
||||
createComment({
|
||||
targetType: comment.targetType,
|
||||
targetId: comment.targetId,
|
||||
content: commentContent,
|
||||
replyId: comment.id,
|
||||
isPrivate,
|
||||
showClientInfo
|
||||
}).then(() => {
|
||||
toast.success(t("comment_success"));
|
||||
reloadReplies();
|
||||
|
@ -61,18 +61,18 @@ export function CommentSection(
|
||||
});
|
||||
}, [])
|
||||
|
||||
const onCommentSubmitted = ({ commentContent, isPrivate }: { commentContent: string, isPrivate: boolean }) => {
|
||||
const onCommentSubmitted = ({ commentContent, isPrivate, showClientInfo }: { commentContent: string, isPrivate: boolean, showClientInfo: boolean }) => {
|
||||
createComment({
|
||||
targetType,
|
||||
targetId,
|
||||
content: commentContent,
|
||||
replyId: null,
|
||||
isPrivate,
|
||||
showClientInfo
|
||||
}).then(res => {
|
||||
toast.success(t("comment_success"));
|
||||
setTotalCommentCount(c => c + 1);
|
||||
getComment({ id: res.data.id }).then(response => {
|
||||
console.log("New comment fetched:", response.data);
|
||||
setComments(prevComments => [response.data, ...prevComments]);
|
||||
});
|
||||
setActiveInput(null);
|
||||
|
@ -47,6 +47,7 @@
|
||||
"private": "私密评论",
|
||||
"private_placeholder": "悄悄地说一句...",
|
||||
"reply": "回复",
|
||||
"show_client_info": "展示客户端信息",
|
||||
"submit": "提交",
|
||||
"unlike": "取消点赞",
|
||||
"unlike_success": "已取消点赞",
|
||||
|
Reference in New Issue
Block a user