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