gophertype/pkg/models/comments.go

140 lines
3.3 KiB
Go
Raw Normal View History

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"
}
}