Noah
ceb42aa4d0
* List comments on a post * Add comments, with preview * Users can edit their own comments (EditToken) * Admin can edit all comments * Delete comments * Comment counts on main blog index pages
140 lines
3.3 KiB
Go
140 lines
3.3 KiB
Go
package models
|
|
|
|
import (
|
|
"crypto/md5"
|
|
"fmt"
|
|
"html/template"
|
|
"io"
|
|
"net/mail"
|
|
|
|
"git.kirsle.net/apps/gophertype/pkg/console"
|
|
"git.kirsle.net/apps/gophertype/pkg/markdown"
|
|
"github.com/albrow/forms"
|
|
"github.com/jinzhu/gorm"
|
|
uuid "github.com/satori/go.uuid"
|
|
)
|
|
|
|
type commentMan struct{}
|
|
|
|
// Comments is a singleton manager class for Comment model access.
|
|
var Comments = commentMan{}
|
|
|
|
// Comment model.
|
|
type Comment struct {
|
|
gorm.Model
|
|
|
|
Thread string `gorm:"index"` // name of comment thread
|
|
UserID uint // foreign key to User.ID
|
|
Name string
|
|
Email string
|
|
Avatar string
|
|
Body string
|
|
EditToken string // So users can edit their own recent comments.
|
|
DeleteToken string `gorm:"unique_index"` // Quick-delete token for spam.
|
|
|
|
User User `gorm:"foreign_key:UserID"`
|
|
}
|
|
|
|
// New creates a new Comment model.
|
|
func (m commentMan) New() Comment {
|
|
return Comment{
|
|
DeleteToken: uuid.NewV4().String(),
|
|
}
|
|
}
|
|
|
|
// Load a comment by ID.
|
|
func (m commentMan) Load(id int) (Comment, error) {
|
|
var com Comment
|
|
r := DB.Preload("User").First(&com, id)
|
|
return com, r.Error
|
|
}
|
|
|
|
// LoadByDeleteToken loads a comment by its DeleteToken.
|
|
func (m commentMan) LoadByDeleteToken(token string) (Comment, error) {
|
|
var com Comment
|
|
r := DB.Preload("User").Where("delete_token = ?", token).First(&com)
|
|
return com, r.Error
|
|
}
|
|
|
|
// GetIndex returns the index page of blog posts.
|
|
func (m commentMan) GetThread(thread string) ([]Comment, error) {
|
|
var coms []Comment
|
|
r := DB.Debug().Preload("User").
|
|
Where("thread = ?", thread).
|
|
Order("created_at asc").
|
|
Find(&coms)
|
|
return coms, r.Error
|
|
}
|
|
|
|
// HTML returns the comment's body as rendered HTML code.
|
|
func (c Comment) HTML() template.HTML {
|
|
return template.HTML(markdown.RenderMarkdown(c.Body))
|
|
}
|
|
|
|
// Save a comment.
|
|
func (c *Comment) Save() error {
|
|
// Ensure the delete token is unique!
|
|
{
|
|
if exist, err := Comments.LoadByDeleteToken(c.DeleteToken); err != nil && exist.ID != c.ID {
|
|
console.Debug("Comment.Save: delete token is not unique, trying to resolve")
|
|
var resolved bool
|
|
for i := 2; i <= 100; i++ {
|
|
token := uuid.NewV4().String()
|
|
_, err = Comments.LoadByDeleteToken(token)
|
|
if err == nil {
|
|
continue
|
|
}
|
|
|
|
c.DeleteToken = token
|
|
resolved = true
|
|
break
|
|
}
|
|
|
|
if !resolved {
|
|
return fmt.Errorf("failed to generate a unique delete token after 100 attempts")
|
|
}
|
|
}
|
|
}
|
|
|
|
console.Info("Save comment: %+v", c)
|
|
|
|
// Save the post.
|
|
if DB.NewRecord(c) {
|
|
console.Warn("NEw Record!")
|
|
return DB.Create(&c).Error
|
|
}
|
|
|
|
return DB.Save(&c).Error
|
|
}
|
|
|
|
// Delete a comment.
|
|
func (c Comment) Delete() error {
|
|
return DB.Delete(&c).Error
|
|
}
|
|
|
|
// ParseForm populates a Post from an HTTP form.
|
|
func (c *Comment) ParseForm(form *forms.Data) {
|
|
c.Thread = form.Get("thread")
|
|
c.Name = form.Get("name")
|
|
c.Email = form.Get("email")
|
|
c.Body = form.Get("body")
|
|
c.LoadAvatar()
|
|
}
|
|
|
|
// LoadAvatar calculates the user's avatar for the comment.
|
|
func (c *Comment) LoadAvatar() {
|
|
// MD5 hash the email address for Gravatar.
|
|
if _, err := mail.ParseAddress(c.Email); err == nil {
|
|
h := md5.New()
|
|
io.WriteString(h, c.Email)
|
|
hash := fmt.Sprintf("%x", h.Sum(nil))
|
|
c.Avatar = fmt.Sprintf(
|
|
"//www.gravatar.com/avatar/%s?s=96",
|
|
hash,
|
|
)
|
|
} else {
|
|
// Default gravatar.
|
|
c.Avatar = "https://www.gravatar.com/avatar/00000000000000000000000000000000"
|
|
}
|
|
}
|