feat(posts): add post reference/internal linking functionality
All checks were successful
Build Backend / build (push) Successful in 2m19s
Build Backend / build-docker (push) Successful in 1m18s

Add support for referencing other posts within messages through a new post_ref segment type. Includes new PostReference model to track relationships, PostRefService for processing references, and new endpoints for post suggestions, related posts, and reference click tracking.
This commit is contained in:
lafay
2026-04-26 00:37:20 +08:00
parent 898c0e6d9c
commit 23d7f1151e
11 changed files with 592 additions and 5 deletions

View File

@@ -0,0 +1,97 @@
package repository
import (
"context"
"gorm.io/gorm"
"gorm.io/gorm/clause"
"with_you/internal/model"
)
type PostRefRepository interface {
BatchUpsert(ctx context.Context, sourcePostID string, targetPostIDs []string) error
DeleteBySourcePostID(ctx context.Context, sourcePostID string) error
GetSourcesByTargetPostID(ctx context.Context, targetPostID string, page, pageSize int) ([]model.PostReference, int64, error)
GetTargetsBySourcePostID(ctx context.Context, sourcePostID string) ([]model.PostReference, error)
MarkStatusByTargetPostID(ctx context.Context, targetPostID string, status model.PostReferenceStatus) error
IncrementClickCount(ctx context.Context, sourcePostID, targetPostID string) error
}
type postRefRepository struct {
db *gorm.DB
}
func NewPostRefRepository(db *gorm.DB) PostRefRepository {
return &postRefRepository{db: db}
}
func (r *postRefRepository) BatchUpsert(ctx context.Context, sourcePostID string, targetPostIDs []string) error {
if len(targetPostIDs) == 0 {
return nil
}
refs := make([]model.PostReference, 0, len(targetPostIDs))
for _, targetID := range targetPostIDs {
refs = append(refs, model.PostReference{
SourcePostID: sourcePostID,
TargetPostID: targetID,
Status: model.PostRefActive,
})
}
return r.db.WithContext(ctx).Clauses(clause.OnConflict{
Columns: []clause.Column{{Name: "source_post_id"}, {Name: "target_post_id"}},
DoUpdates: clause.AssignmentColumns([]string{"status", "updated_at"}),
}).Create(&refs).Error
}
func (r *postRefRepository) DeleteBySourcePostID(ctx context.Context, sourcePostID string) error {
return r.db.WithContext(ctx).Where("source_post_id = ?", sourcePostID).Delete(&model.PostReference{}).Error
}
func (r *postRefRepository) GetSourcesByTargetPostID(ctx context.Context, targetPostID string, page, pageSize int) ([]model.PostReference, int64, error) {
var refs []model.PostReference
var total int64
if err := r.db.WithContext(ctx).Model(&model.PostReference{}).
Where("target_post_id = ? AND status = ?", targetPostID, model.PostRefActive).
Count(&total).Error; err != nil {
return nil, 0, err
}
offset := (page - 1) * pageSize
if err := r.db.WithContext(ctx).
Where("target_post_id = ? AND status = ?", targetPostID, model.PostRefActive).
Preload("SourcePost").
Preload("SourcePost.User").
Order("created_at DESC").
Offset(offset).Limit(pageSize).
Find(&refs).Error; err != nil {
return nil, 0, err
}
return refs, total, nil
}
func (r *postRefRepository) GetTargetsBySourcePostID(ctx context.Context, sourcePostID string) ([]model.PostReference, error) {
var refs []model.PostReference
if err := r.db.WithContext(ctx).
Where("source_post_id = ?", sourcePostID).
Preload("TargetPost").
Preload("TargetPost.User").
Find(&refs).Error; err != nil {
return nil, err
}
return refs, nil
}
func (r *postRefRepository) MarkStatusByTargetPostID(ctx context.Context, targetPostID string, status model.PostReferenceStatus) error {
return r.db.WithContext(ctx).Model(&model.PostReference{}).
Where("target_post_id = ?", targetPostID).
Update("status", status).Error
}
func (r *postRefRepository) IncrementClickCount(ctx context.Context, sourcePostID, targetPostID string) error {
return r.db.WithContext(ctx).Model(&model.PostReference{}).
Where("source_post_id = ? AND target_post_id = ?", sourcePostID, targetPostID).
UpdateColumn("click_count", gorm.Expr("click_count + 1")).Error
}