2019-11-18 15:13:51 +09:00

255 lines
5.4 KiB
Go

package arn
import (
"fmt"
"reflect"
"sort"
"strings"
"github.com/aerogo/markdown"
"github.com/aerogo/nano"
)
// PostID represents a post ID.
type PostID = ID
// Post is a comment related to any parent type in the database.
type Post struct {
Tags []string `json:"tags" editable:"true"`
ParentID ID `json:"parentId" editable:"true"`
ParentType string `json:"parentType"`
Edited string `json:"edited"`
hasID
hasText
hasPosts
hasCreator
hasLikes
html string
}
// Parent returns the object this post was posted in.
func (post *Post) Parent() PostParent {
obj, _ := DB.Get(post.ParentType, post.ParentID)
return obj.(PostParent)
}
// TopMostParent returns the first non-post object this post was posted in.
func (post *Post) TopMostParent() PostParent {
topMostParent := post.Parent()
for {
if topMostParent.TypeName() != "Post" {
return topMostParent
}
newParent := topMostParent.(*Post).Parent()
if newParent == nil {
return topMostParent
}
topMostParent = newParent
}
}
// GetParentID returns the object ID of the parent.
func (post *Post) GetParentID() string {
return post.ParentID
}
// GetParentType returns the object type of the parent.
func (post *Post) GetParentType() string {
return post.ParentType
}
// SetParent sets a new parent.
func (post *Post) SetParent(newParent PostParent) {
// Remove from old parent
oldParent := post.Parent()
oldParent.RemovePost(post.ID)
oldParent.Save()
// Update own fields
post.ParentID = newParent.GetID()
post.ParentType = reflect.TypeOf(newParent).Elem().Name()
// Add to new parent
newParent.AddPost(post.ID)
newParent.Save()
}
// Link returns the relative URL of the post.
func (post *Post) Link() string {
return "/post/" + post.ID
}
// TypeName returns the type name.
func (post *Post) TypeName() string {
return "Post"
}
// Self returns the object itself.
func (post *Post) Self() Loggable {
return post
}
// TitleByUser returns the preferred title for the given user.
func (post *Post) TitleByUser(user *User) string {
return post.Creator().Nick + "'s comment"
}
// HTML returns the HTML representation of the post.
func (post *Post) HTML() string {
if post.html != "" {
return post.html
}
post.html = markdown.Render(post.Text)
return post.html
}
// String implements the default string serialization.
func (post *Post) String() string {
const maxLen = 170
if len(post.Text) > maxLen {
return post.Text[:maxLen-3] + "..."
}
return post.Text
}
// OnLike is called when the post receives a like.
func (post *Post) OnLike(likedBy *User) {
if !post.Creator().Settings().Notification.ForumLikes {
return
}
go func() {
message := ""
notifyUser := post.Creator()
if post.ParentType == "User" {
if post.ParentID == notifyUser.ID {
// Somebody liked your post on your own profile
message = fmt.Sprintf(`%s liked your profile post.`, likedBy.Nick)
} else {
// Somebody liked your post on someone else's profile
message = fmt.Sprintf(`%s liked your post on %s's profile.`, likedBy.Nick, post.Parent().TitleByUser(notifyUser))
}
} else {
message = fmt.Sprintf(`%s liked your post in the %s "%s".`, likedBy.Nick, strings.ToLower(post.ParentType), post.Parent().TitleByUser(notifyUser))
}
notifyUser.SendNotification(&PushNotification{
Title: likedBy.Nick + " liked your post",
Message: message,
Icon: "https:" + likedBy.AvatarLink("large"),
Link: "https://notify.moe" + likedBy.Link(),
Type: NotificationTypeLike,
})
}()
}
// GetPost ...
func GetPost(id PostID) (*Post, error) {
obj, err := DB.Get("Post", id)
if err != nil {
return nil, err
}
return obj.(*Post), nil
}
// StreamPosts returns a stream of all posts.
func StreamPosts() <-chan *Post {
channel := make(chan *Post, nano.ChannelBufferSize)
go func() {
for obj := range DB.All("Post") {
channel <- obj.(*Post)
}
close(channel)
}()
return channel
}
// AllPosts returns a slice of all posts.
func AllPosts() []*Post {
all := make([]*Post, 0, DB.Collection("Post").Count())
for obj := range StreamPosts() {
all = append(all, obj)
}
return all
}
// SortPostsLatestFirst sorts the slice of posts.
func SortPostsLatestFirst(posts []*Post) {
sort.Slice(posts, func(i, j int) bool {
return posts[i].Created > posts[j].Created
})
}
// SortPostsLatestLast sorts the slice of posts.
func SortPostsLatestLast(posts []*Post) {
sort.Slice(posts, func(i, j int) bool {
return posts[i].Created < posts[j].Created
})
}
// FilterPostsWithUniqueThreads removes posts with the same thread until we have enough posts.
func FilterPostsWithUniqueThreads(posts []*Post, limit int) []*Post {
filtered := []*Post{}
threadsProcessed := map[string]bool{}
for _, post := range posts {
if len(filtered) >= limit {
return filtered
}
_, found := threadsProcessed[post.ParentID]
if found {
continue
}
threadsProcessed[post.ParentID] = true
filtered = append(filtered, post)
}
return filtered
}
// GetPostsByUser ...
func GetPostsByUser(user *User) ([]*Post, error) {
var posts []*Post
for post := range StreamPosts() {
if post.CreatedBy == user.ID {
posts = append(posts, post)
}
}
return posts, nil
}
// FilterPosts filters all forum posts by a custom function.
func FilterPosts(filter func(*Post) bool) ([]*Post, error) {
var filtered []*Post
for post := range StreamPosts() {
if filter(post) {
filtered = append(filtered, post)
}
}
return filtered, nil
}