package models import ( "time" "gorm.io/gorm" ) // ArticleView represents a unique article view record. // Each record tracks one unique visit (by IP or user) to an article. type ArticleView struct { ID uint `gorm:"primarykey" json:"id"` CreatedAt time.Time `json:"created_at"` ArticleID uint `gorm:"not null;index:idx_article_views_article" json:"article_id"` UserID *uint `gorm:"index:idx_article_views_user" json:"user_id"` // NULL for anonymous users IP string `gorm:"size:64;not null;index:idx_article_views_ip" json:"ip"` UserAgent string `gorm:"size:512" json:"user_agent"` IsBot bool `gorm:"default:false;index:idx_article_views_bot" json:"is_bot"` Article Article `gorm:"foreignKey:ArticleID" json:"-"` User *User `gorm:"foreignKey:UserID" json:"-"` } // TableName overrides the default GORM table name. func (ArticleView) TableName() string { return "article_views" } // BeforeCreate hook to ensure we don't create duplicate records. // This is a safety check in addition to application-level deduplication. func (av *ArticleView) BeforeCreate(tx *gorm.DB) error { var count int64 query := tx.Model(&ArticleView{}). Where("article_id = ? AND ip = ?", av.ArticleID, av.IP) if av.UserID != nil { query = query.Where("user_id = ?", *av.UserID) } else { query = query.Where("user_id IS NULL") } query.Count(&count) if count > 0 { // Record already exists, skip creation return gorm.ErrDuplicatedKey } return nil }