feat(blog): add view count tracking to blog posts
All checks were successful
Build and Release / Create Release (push) Successful in 0s
Build and Release / Unit Tests (push) Successful in 3m22s
Build and Release / Integration Tests (PostgreSQL) (push) Successful in 4m10s
Build and Release / Lint (push) Successful in 4m54s
Build and Release / Build Binaries (amd64, linux, linux-latest) (push) Successful in 3m0s
Build and Release / Build Binaries (amd64, windows, windows-latest) (push) Successful in 9h4m43s
Build and Release / Build Binaries (amd64, darwin, macos) (push) Successful in 6m39s
Build and Release / Build Binaries (arm64, darwin, macos) (push) Successful in 7m46s
Build and Release / Build Binary (linux/arm64) (push) Successful in 9m6s
All checks were successful
Build and Release / Create Release (push) Successful in 0s
Build and Release / Unit Tests (push) Successful in 3m22s
Build and Release / Integration Tests (PostgreSQL) (push) Successful in 4m10s
Build and Release / Lint (push) Successful in 4m54s
Build and Release / Build Binaries (amd64, linux, linux-latest) (push) Successful in 3m0s
Build and Release / Build Binaries (amd64, windows, windows-latest) (push) Successful in 9h4m43s
Build and Release / Build Binaries (amd64, darwin, macos) (push) Successful in 6m39s
Build and Release / Build Binaries (arm64, darwin, macos) (push) Successful in 7m46s
Build and Release / Build Binary (linux/arm64) (push) Successful in 9m6s
Adds view_count field to blog_post table with database migration. Implements atomic increment on post views in both standalone and repo blog routes. Displays view count with eye icon in post templates.
This commit is contained in:
@@ -56,6 +56,7 @@ type BlogPost struct { //revive:disable-line:exported
|
||||
Status BlogPostStatus `xorm:"SMALLINT NOT NULL DEFAULT 0"`
|
||||
AllowComments bool `xorm:"NOT NULL DEFAULT true"`
|
||||
SubscriptionOnly bool `xorm:"NOT NULL DEFAULT false"`
|
||||
ViewCount int64 `xorm:"NOT NULL DEFAULT 0"`
|
||||
PublishedUnix timeutil.TimeStamp `xorm:"INDEX"`
|
||||
CreatedUnix timeutil.TimeStamp `xorm:"INDEX created"`
|
||||
UpdatedUnix timeutil.TimeStamp `xorm:"INDEX updated"`
|
||||
@@ -437,6 +438,12 @@ func DeleteBlogPost(ctx context.Context, id int64) error {
|
||||
return err
|
||||
}
|
||||
|
||||
// IncrementBlogPostViewCount atomically increments the view count for a blog post.
|
||||
func IncrementBlogPostViewCount(ctx context.Context, id int64) error {
|
||||
_, err := db.GetEngine(ctx).Exec("UPDATE blog_post SET view_count = view_count + 1 WHERE id = ?", id)
|
||||
return err
|
||||
}
|
||||
|
||||
// CountPublishedBlogPostsByAuthorID returns the number of published/public blog posts by a user.
|
||||
func CountPublishedBlogPostsByAuthorID(ctx context.Context, authorID int64) (int64, error) {
|
||||
return db.GetEngine(ctx).Where("author_id = ? AND status >= ?", authorID, BlogPostPublic).Count(new(BlogPost))
|
||||
|
||||
@@ -438,6 +438,7 @@ func prepareMigrationTasks() []*migration {
|
||||
newMigration(361, "Create wishlist_comment table", v1_26.CreateWishlistCommentTable),
|
||||
newMigration(362, "Create wishlist_comment_reaction table", v1_26.CreateWishlistCommentReactionTable),
|
||||
newMigration(363, "Add keep_packages_private to user", v1_26.AddKeepPackagesPrivateToUser),
|
||||
newMigration(364, "Add view_count to blog_post", v1_26.AddViewCountToBlogPost),
|
||||
}
|
||||
return preparedMigrations
|
||||
}
|
||||
|
||||
13
models/migrations/v1_26/v364.go
Normal file
13
models/migrations/v1_26/v364.go
Normal file
@@ -0,0 +1,13 @@
|
||||
// Copyright 2026 MarketAlly. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package v1_26
|
||||
|
||||
import "xorm.io/xorm"
|
||||
|
||||
func AddViewCountToBlogPost(x *xorm.Engine) error {
|
||||
type BlogPost struct {
|
||||
ViewCount int64 `xorm:"NOT NULL DEFAULT 0"`
|
||||
}
|
||||
return x.Sync(new(BlogPost))
|
||||
}
|
||||
@@ -2040,6 +2040,7 @@
|
||||
"repo.blog.subscription_required_desc": "This post is available exclusively to subscribers. Subscribe to unlock the full content.",
|
||||
"repo.blog.subscribe_to_read": "Subscribe to Read",
|
||||
"repo.blog.reactions.admin_hint": "Thumbs down counts are only visible to repo admins.",
|
||||
"repo.blog.views": "views",
|
||||
"repo.blog.comments": "Comments",
|
||||
"repo.blog.comments.empty": "No comments yet. Be the first to share your thoughts!",
|
||||
"repo.blog.comments.disabled": "Comments have been disabled for this post.",
|
||||
|
||||
@@ -168,6 +168,10 @@ func StandaloneBlogView(ctx *context.Context) {
|
||||
}
|
||||
}
|
||||
|
||||
// Increment view count
|
||||
_ = blog_model.IncrementBlogPostViewCount(ctx, post.ID)
|
||||
post.ViewCount++
|
||||
|
||||
if err := post.LoadAuthor(ctx); err != nil {
|
||||
ctx.ServerError("LoadAuthor", err)
|
||||
return
|
||||
|
||||
@@ -229,6 +229,10 @@ func BlogView(ctx *context.Context) {
|
||||
}
|
||||
}
|
||||
|
||||
// Increment view count
|
||||
_ = blog_model.IncrementBlogPostViewCount(ctx, post.ID)
|
||||
post.ViewCount++
|
||||
|
||||
if err := post.LoadAuthor(ctx); err != nil {
|
||||
ctx.ServerError("LoadAuthor", err)
|
||||
return
|
||||
|
||||
@@ -115,6 +115,10 @@
|
||||
{{if .IsWriter}}
|
||||
<span class="blog-reaction-hint">{{ctx.Locale.Tr "repo.blog.reactions.admin_hint"}}</span>
|
||||
{{end}}
|
||||
<span class="blog-view-count">
|
||||
{{svg "octicon-eye" 16}}
|
||||
<span>{{.BlogPost.ViewCount}}</span>
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<!-- Comments Section -->
|
||||
@@ -510,6 +514,14 @@
|
||||
font-size: 12px;
|
||||
color: var(--color-text-light-3);
|
||||
}
|
||||
.blog-view-count {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
margin-left: auto;
|
||||
font-size: 14px;
|
||||
color: var(--color-text-light-3);
|
||||
}
|
||||
/* Comments */
|
||||
.blog-comments {
|
||||
margin-bottom: 32px;
|
||||
|
||||
@@ -108,6 +108,10 @@
|
||||
{{if .IsWriter}}
|
||||
<span class="blog-reaction-hint">{{ctx.Locale.Tr "repo.blog.reactions.admin_hint"}}</span>
|
||||
{{end}}
|
||||
<span class="blog-view-count">
|
||||
{{svg "octicon-eye" 16}}
|
||||
<span>{{.BlogPost.ViewCount}}</span>
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<!-- Comments Section -->
|
||||
@@ -477,6 +481,14 @@
|
||||
font-size: 12px;
|
||||
color: var(--color-text-light-3);
|
||||
}
|
||||
.blog-view-count {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
margin-left: auto;
|
||||
font-size: 14px;
|
||||
color: var(--color-text-light-3);
|
||||
}
|
||||
/* Comments */
|
||||
.blog-comments {
|
||||
margin-bottom: 32px;
|
||||
|
||||
Reference in New Issue
Block a user