feat(posts): add preview URL fields for post images
All checks were successful
Build Backend / build (push) Successful in 4m45s
Build Backend / build-docker (push) Successful in 1m31s

Add PreviewURL and PreviewURLLarge fields to PostImage model and responses. Image data now supports format "url|preview_url|preview_url_large" for storing multiple image variants. Also change comment cursor pagination to sort by created_at ASC (oldest first) to display earliest comments as first floor, and make AdminReviewVerificationRequest.Approve field optional.
This commit is contained in:
lafay
2026-04-10 01:13:58 +08:00
parent 539bec6f63
commit 6af6aaa9d0
5 changed files with 57 additions and 28 deletions

View File

@@ -1404,7 +1404,7 @@ type AdminVerificationDetailResponse struct {
// AdminReviewVerificationRequest 管理端审核认证请求 // AdminReviewVerificationRequest 管理端审核认证请求
type AdminReviewVerificationRequest struct { type AdminReviewVerificationRequest struct {
Approve bool `json:"approve" binding:"required"` Approve bool `json:"approve"`
RejectReason string `json:"reject_reason" binding:"omitempty,max=500"` RejectReason string `json:"reject_reason" binding:"omitempty,max=500"`
} }

View File

@@ -14,11 +14,13 @@ func ConvertPostImageToResponse(img *model.PostImage) PostImageResponse {
return PostImageResponse{} return PostImageResponse{}
} }
return PostImageResponse{ return PostImageResponse{
ID: img.ID, ID: img.ID,
URL: img.URL, URL: img.URL,
ThumbnailURL: img.ThumbnailURL, ThumbnailURL: img.ThumbnailURL,
Width: img.Width, PreviewURL: img.PreviewURL,
Height: img.Height, PreviewURLLarge: img.PreviewURLLarge,
Width: img.Width,
Height: img.Height,
} }
} }

View File

@@ -495,14 +495,14 @@ func (r *commentRepository) GetCommentsByPostIDForAdmin(postID string, page, pag
// ========== Cursor Pagination Methods ========== // ========== Cursor Pagination Methods ==========
// GetCommentsByCursor 游标分页获取帖子评论(包含回复) // GetCommentsByCursor 游标分页获取帖子评论(包含回复)
// 排序方式created_at DESC // 排序方式created_at ASC最早发布的为一楼
func (r *commentRepository) GetCommentsByCursor(ctx context.Context, postID string, req *cursor.PageRequest) (*cursor.CursorPageResult[*model.Comment], error) { func (r *commentRepository) GetCommentsByCursor(ctx context.Context, postID string, req *cursor.PageRequest) (*cursor.CursorPageResult[*model.Comment], error) {
// 构建基础查询 - 只查询顶级评论 // 构建基础查询 - 只查询顶级评论
query := r.db.WithContext(ctx).Model(&model.Comment{}). query := r.db.WithContext(ctx).Model(&model.Comment{}).
Where("post_id = ? AND parent_id IS NULL AND status = ?", postID, model.CommentStatusPublished) Where("post_id = ? AND parent_id IS NULL AND status = ?", postID, model.CommentStatusPublished)
// 使用游标构建器 // 使用游标构建器
builder := cursor.NewBuilder(query, cursor.SortByCreatedAtDesc). builder := cursor.NewBuilder(query, cursor.SortByCreatedAtAsc).
WithCursor(req.Cursor, req.Direction). WithCursor(req.Cursor, req.Direction).
WithPageSize(req.PageSize) WithPageSize(req.PageSize)
@@ -539,7 +539,7 @@ func (r *commentRepository) GetCommentsByCursor(ctx context.Context, postID stri
nextCursor = cursor.NewCursor( nextCursor = cursor.NewCursor(
cursor.FormatTime(lastComment.CreatedAt), cursor.FormatTime(lastComment.CreatedAt),
lastComment.ID, lastComment.ID,
cursor.SortByCreatedAtDesc, cursor.SortByCreatedAtAsc,
).Encode() ).Encode()
} }
// 上一页游标 // 上一页游标
@@ -547,7 +547,7 @@ func (r *commentRepository) GetCommentsByCursor(ctx context.Context, postID stri
prevCursor = cursor.NewCursor( prevCursor = cursor.NewCursor(
cursor.FormatTime(firstComment.CreatedAt), cursor.FormatTime(firstComment.CreatedAt),
firstComment.ID, firstComment.ID,
cursor.SortByCreatedAtDesc, cursor.SortByCreatedAtAsc,
).Encode() ).Encode()
} }

View File

@@ -148,11 +148,22 @@ func (r *postRepository) UpdateWithImages(post *model.Post, images *[]string) er
return err return err
} }
for i, url := range *images { for i, imageData := range *images {
parts := strings.Split(imageData, "|")
url := parts[0]
var previewURL, previewURLLarge string
if len(parts) >= 2 && parts[1] != "" {
previewURL = parts[1]
}
if len(parts) >= 3 && parts[2] != "" {
previewURLLarge = parts[2]
}
image := &model.PostImage{ image := &model.PostImage{
PostID: post.ID, PostID: post.ID,
URL: url, URL: url,
SortOrder: i, PreviewURL: previewURL,
PreviewURLLarge: previewURLLarge,
SortOrder: i,
} }
if err := tx.Create(image).Error; err != nil { if err := tx.Create(image).Error; err != nil {
return err return err
@@ -695,11 +706,23 @@ func (r *postRepository) createWithTx(tx *gorm.DB, post *model.Post, images []st
} }
// 创建图片记录 // 创建图片记录
for i, url := range images { // 支持格式:["url", "url|preview_url|preview_url_large", ...]
for i, imageData := range images {
parts := strings.Split(imageData, "|")
url := parts[0]
var previewURL, previewURLLarge string
if len(parts) >= 2 && parts[1] != "" {
previewURL = parts[1]
}
if len(parts) >= 3 && parts[2] != "" {
previewURLLarge = parts[2]
}
image := &model.PostImage{ image := &model.PostImage{
PostID: post.ID, PostID: post.ID,
URL: url, URL: url,
SortOrder: i, PreviewURL: previewURL,
PreviewURLLarge: previewURLLarge,
SortOrder: i,
} }
if err := tx.Create(image).Error; err != nil { if err := tx.Create(image).Error; err != nil {
return err return err

View File

@@ -256,11 +256,13 @@ func convertPostToAdminListResponse(post *model.Post) dto.AdminPostListResponse
images := make([]dto.PostImageResponse, len(post.Images)) images := make([]dto.PostImageResponse, len(post.Images))
for i, img := range post.Images { for i, img := range post.Images {
images[i] = dto.PostImageResponse{ images[i] = dto.PostImageResponse{
ID: img.ID, ID: img.ID,
URL: img.URL, URL: img.URL,
ThumbnailURL: img.ThumbnailURL, ThumbnailURL: img.ThumbnailURL,
Width: img.Width, PreviewURL: img.PreviewURL,
Height: img.Height, PreviewURLLarge: img.PreviewURLLarge,
Width: img.Width,
Height: img.Height,
} }
} }
@@ -298,11 +300,13 @@ func convertPostToAdminDetailResponse(post *model.Post) *dto.AdminPostDetailResp
images := make([]dto.PostImageResponse, len(post.Images)) images := make([]dto.PostImageResponse, len(post.Images))
for i, img := range post.Images { for i, img := range post.Images {
images[i] = dto.PostImageResponse{ images[i] = dto.PostImageResponse{
ID: img.ID, ID: img.ID,
URL: img.URL, URL: img.URL,
ThumbnailURL: img.ThumbnailURL, ThumbnailURL: img.ThumbnailURL,
Width: img.Width, PreviewURL: img.PreviewURL,
Height: img.Height, PreviewURLLarge: img.PreviewURLLarge,
Width: img.Width,
Height: img.Height,
} }
} }