feat(api): add message segments support for posts and comments
All checks were successful
Build Backend / build (push) Successful in 19m52s
Build Backend / build-docker (push) Successful in 1m29s

Add rich message segment support enabling structured content (text, images, votes, mentions) in posts and comments. Includes segments field in models, DTOs, handlers, and services. Also adds @mention notification processing and migrates vote data to embedded segments.
This commit is contained in:
lafay
2026-04-23 22:29:34 +08:00
parent 15e7c99353
commit 05c7f8a5eb
14 changed files with 364 additions and 154 deletions

View File

@@ -1,4 +1,4 @@
package handler
package handler
import (
"encoding/json"
@@ -7,6 +7,7 @@ import (
"github.com/gin-gonic/gin"
"with_you/internal/dto"
"with_you/internal/model"
"with_you/internal/pkg/cursor"
"with_you/internal/pkg/response"
"with_you/internal/service"
@@ -33,10 +34,11 @@ func (h *CommentHandler) Create(c *gin.Context) {
}
type CreateRequest struct {
PostID string `json:"post_id" binding:"required"`
Content string `json:"content"` // 内容可选,允许只发图片
ParentID *string `json:"parent_id"`
Images []string `json:"images"` // 图片URL列表
PostID string `json:"post_id" binding:"required"`
Content string `json:"content"`
Segments model.MessageSegments `json:"segments"`
ParentID *string `json:"parent_id"`
Images []string `json:"images"`
}
var req CreateRequest
@@ -45,12 +47,18 @@ func (h *CommentHandler) Create(c *gin.Context) {
return
}
// 验证:评论必须有内容或图片
if req.Content == "" && len(req.Images) == 0 {
// 验证:评论必须有内容或图片或segments
if req.Content == "" && len(req.Images) == 0 && len(req.Segments) == 0 {
response.BadRequest(c, "评论内容或图片不能同时为空")
return
}
// 如果没有content但有segments从segments提取文本
content := req.Content
if content == "" && len(req.Segments) > 0 {
content = dto.ExtractTextContent(req.Segments)
}
// 将图片列表转换为JSON字符串
var imagesJSON string
if len(req.Images) > 0 {
@@ -58,7 +66,7 @@ func (h *CommentHandler) Create(c *gin.Context) {
imagesJSON = string(imagesBytes)
}
comment, err := h.commentService.Create(c.Request.Context(), req.PostID, userID, req.Content, req.ParentID, imagesJSON, req.Images)
comment, err := h.commentService.Create(c.Request.Context(), req.PostID, userID, content, req.Segments, req.ParentID, imagesJSON, req.Images)
if err != nil {
response.InternalServerError(c, "failed to create comment")
return

View File

@@ -1,4 +1,4 @@
package handler
package handler
import (
"errors"
@@ -52,20 +52,21 @@ func (h *PostHandler) Create(c *gin.Context) {
return
}
type CreateRequest struct {
Title string `json:"title" binding:"required"`
Content string `json:"content" binding:"required"`
Images []string `json:"images"`
ChannelID *string `json:"channel_id,omitempty"`
}
var req CreateRequest
var req dto.CreatePostWithSegmentsRequest
if err := c.ShouldBindJSON(&req); err != nil {
response.BadRequest(c, err.Error())
return
}
post, err := h.postService.Create(c.Request.Context(), userID, req.Title, req.Content, req.Images, req.ChannelID)
content := req.Content
segments := req.Segments
if len(segments) == 0 && content == "" {
response.BadRequest(c, "content or segments is required")
return
}
post, err := h.postService.Create(c.Request.Context(), userID, req.Title, content, segments, req.Images, req.ChannelID)
if err != nil {
var moderationErr *service.PostModerationRejectedError
if errors.As(err, &moderationErr) {
@@ -123,6 +124,7 @@ func (h *PostHandler) GetByID(c *gin.Context) {
ChannelID: post.ChannelID,
Title: post.Title,
Content: post.Content,
Segments: dto.SegmentsOrDefault(post.Segments, post.Content),
Images: dto.ConvertPostImagesToResponse(post.Images),
Status: string(post.Status),
LikesCount: post.LikesCount,
@@ -365,9 +367,10 @@ func (h *PostHandler) Update(c *gin.Context) {
}
type UpdateRequest struct {
Title string `json:"title"`
Content string `json:"content"`
Images *[]string `json:"images"`
Title string `json:"title"`
Content string `json:"content"`
Segments model.MessageSegments `json:"segments"`
Images *[]string `json:"images"`
}
var req UpdateRequest
@@ -382,6 +385,12 @@ func (h *PostHandler) Update(c *gin.Context) {
if req.Content != "" {
post.Content = req.Content
}
if len(req.Segments) > 0 {
post.Segments = req.Segments
if req.Content == "" {
post.Content = dto.ExtractTextContent(req.Segments)
}
}
err = h.postService.UpdateWithImages(c.Request.Context(), post, req.Images)
if err != nil {

View File

@@ -1,4 +1,4 @@
package handler
package handler
import (
"cmp"