feat(blog): add series support and v2 API endpoints
Adds blog series field to group related posts together. Implements v2 API endpoints for listing, creating, updating, and deleting blog posts with proper error codes. Adds series filtering to explore page and sitemap support with pagination. Includes BlogPostV2 structs with author/repo references, HTML URLs, and content rendering. Updates editor UI with series input field.
This commit is contained in:
@@ -157,6 +157,12 @@ const (
|
||||
WikiDisabled ErrorCode = "WIKI_DISABLED"
|
||||
)
|
||||
|
||||
// Blog errors (BLOG_)
|
||||
const (
|
||||
BlogPostNotFound ErrorCode = "BLOG_POST_NOT_FOUND"
|
||||
BlogDisabled ErrorCode = "BLOG_DISABLED"
|
||||
)
|
||||
|
||||
// errorInfo contains metadata about an error code
|
||||
type errorInfo struct {
|
||||
Message string
|
||||
@@ -277,6 +283,10 @@ var errorCatalog = map[ErrorCode]errorInfo{
|
||||
WikiPageAlreadyExists: {"Wiki page already exists", http.StatusConflict},
|
||||
WikiReservedName: {"Wiki page name is reserved", http.StatusBadRequest},
|
||||
WikiDisabled: {"Wiki is disabled for this repository", http.StatusForbidden},
|
||||
|
||||
// Blog errors
|
||||
BlogPostNotFound: {"Blog post not found", http.StatusNotFound},
|
||||
BlogDisabled: {"Blogs are disabled", http.StatusForbidden},
|
||||
}
|
||||
|
||||
// Message returns the human-readable message for an error code
|
||||
|
||||
72
modules/structs/repo_blog_v2.go
Normal file
72
modules/structs/repo_blog_v2.go
Normal file
@@ -0,0 +1,72 @@
|
||||
// Copyright 2026 MarketAlly. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package structs
|
||||
|
||||
import "time"
|
||||
|
||||
// BlogPostV2 represents a blog post in v2 API format
|
||||
type BlogPostV2 struct {
|
||||
ID int64 `json:"id"`
|
||||
Title string `json:"title"`
|
||||
Subtitle string `json:"subtitle,omitempty"`
|
||||
Series string `json:"series,omitempty"`
|
||||
Content string `json:"content,omitempty"`
|
||||
ContentHTML string `json:"content_html,omitempty"`
|
||||
Tags []string `json:"tags"`
|
||||
Status string `json:"status"`
|
||||
AllowComments bool `json:"allow_comments"`
|
||||
FeaturedImageURL string `json:"featured_image_url,omitempty"`
|
||||
Author *BlogAuthorV2 `json:"author,omitempty"`
|
||||
Repo *BlogRepoRefV2 `json:"repo,omitempty"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
UpdatedAt time.Time `json:"updated_at"`
|
||||
PublishedAt *time.Time `json:"published_at,omitempty"`
|
||||
HTMLURL string `json:"html_url"`
|
||||
}
|
||||
|
||||
// BlogAuthorV2 represents the author of a blog post
|
||||
type BlogAuthorV2 struct {
|
||||
ID int64 `json:"id"`
|
||||
Username string `json:"username"`
|
||||
Name string `json:"name"`
|
||||
AvatarURL string `json:"avatar_url"`
|
||||
}
|
||||
|
||||
// BlogRepoRefV2 is a minimal repo reference in blog responses
|
||||
type BlogRepoRefV2 struct {
|
||||
ID int64 `json:"id"`
|
||||
FullName string `json:"full_name"`
|
||||
HTMLURL string `json:"html_url"`
|
||||
}
|
||||
|
||||
// BlogPostListV2 represents a paginated list of blog posts
|
||||
type BlogPostListV2 struct {
|
||||
Posts []*BlogPostV2 `json:"posts"`
|
||||
TotalCount int64 `json:"total_count"`
|
||||
HasMore bool `json:"has_more"`
|
||||
}
|
||||
|
||||
// CreateBlogPostV2Option represents options for creating a blog post
|
||||
type CreateBlogPostV2Option struct {
|
||||
Title string `json:"title" binding:"Required;MaxSize(255)"`
|
||||
Subtitle string `json:"subtitle" binding:"MaxSize(500)"`
|
||||
Series string `json:"series" binding:"MaxSize(255)"`
|
||||
Content string `json:"content" binding:"Required"`
|
||||
Tags []string `json:"tags"`
|
||||
Status string `json:"status"` // draft, public, published
|
||||
AllowComments bool `json:"allow_comments"`
|
||||
FeaturedImageUUID string `json:"featured_image_uuid"`
|
||||
}
|
||||
|
||||
// UpdateBlogPostV2Option represents options for updating a blog post
|
||||
type UpdateBlogPostV2Option struct {
|
||||
Title *string `json:"title" binding:"MaxSize(255)"`
|
||||
Subtitle *string `json:"subtitle" binding:"MaxSize(500)"`
|
||||
Series *string `json:"series" binding:"MaxSize(255)"`
|
||||
Content *string `json:"content"`
|
||||
Tags []string `json:"tags"`
|
||||
Status *string `json:"status"` // draft, public, published
|
||||
AllowComments *bool `json:"allow_comments"`
|
||||
FeaturedImageUUID *string `json:"featured_image_uuid"`
|
||||
}
|
||||
Reference in New Issue
Block a user