✨ New datasets
This commit is contained in:
parent
e0bb05bee8
commit
c23bde901b
1
go.mod
1
go.mod
@ -24,6 +24,7 @@ require (
|
||||
github.com/andybalholm/brotli v1.0.5 // indirect
|
||||
github.com/fsnotify/fsnotify v1.7.0 // indirect
|
||||
github.com/gabriel-vasile/mimetype v1.4.2 // indirect
|
||||
github.com/gertd/go-pluralize v0.2.1 // indirect
|
||||
github.com/go-playground/locales v0.14.1 // indirect
|
||||
github.com/go-playground/universal-translator v0.18.1 // indirect
|
||||
github.com/go-sql-driver/mysql v1.7.1 // indirect
|
||||
|
2
go.sum
2
go.sum
@ -17,6 +17,8 @@ github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nos
|
||||
github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM=
|
||||
github.com/gabriel-vasile/mimetype v1.4.2 h1:w5qFW6JKBz9Y393Y4q372O9A7cUSequkh1Q7OhCmWKU=
|
||||
github.com/gabriel-vasile/mimetype v1.4.2/go.mod h1:zApsH/mKG4w07erKIaJPFiX0Tsq9BFQgN3qGY5GnNgA=
|
||||
github.com/gertd/go-pluralize v0.2.1 h1:M3uASbVjMnTsPb0PNqg+E/24Vwigyo/tvyMTtAlLgiA=
|
||||
github.com/gertd/go-pluralize v0.2.1/go.mod h1:rbYaKDbsXxmRfr8uygAEKhOWsjyrrqrkHVpZvoOp8zk=
|
||||
github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s=
|
||||
github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
|
||||
github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA=
|
||||
|
@ -13,9 +13,15 @@ func RunMigration(source *gorm.DB) error {
|
||||
&models.RealmMember{},
|
||||
&models.Category{},
|
||||
&models.Tag{},
|
||||
&models.Post{},
|
||||
&models.PostLike{},
|
||||
&models.PostDislike{},
|
||||
&models.Moment{},
|
||||
&models.MomentLike{},
|
||||
&models.MomentDislike{},
|
||||
&models.Article{},
|
||||
&models.ArticleLike{},
|
||||
&models.ArticleDislike{},
|
||||
&models.Comment{},
|
||||
&models.CommentLike{},
|
||||
&models.CommentDislike{},
|
||||
&models.Attachment{},
|
||||
); err != nil {
|
||||
return err
|
||||
|
@ -14,10 +14,15 @@ type Account struct {
|
||||
Description string `json:"description"`
|
||||
EmailAddress string `json:"email_address"`
|
||||
PowerLevel int `json:"power_level"`
|
||||
Posts []Post `json:"posts" gorm:"foreignKey:AuthorID"`
|
||||
Moments []Moment `json:"moments" gorm:"foreignKey:AuthorID"`
|
||||
Articles []Article `json:"articles" gorm:"foreignKey:AuthorID"`
|
||||
Attachments []Attachment `json:"attachments" gorm:"foreignKey:AuthorID"`
|
||||
LikedPosts []PostLike `json:"liked_posts"`
|
||||
DislikedPosts []PostDislike `json:"disliked_posts"`
|
||||
LikedMoments []MomentLike `json:"liked_moments"`
|
||||
DislikedMoments []MomentDislike `json:"disliked_moments"`
|
||||
LikedArticles []ArticleLike `json:"liked_articles"`
|
||||
DislikedArticles []ArticleDislike `json:"disliked_articles"`
|
||||
LikedComments []CommentLike `json:"liked_comments"`
|
||||
DislikedComments []CommentDislike `json:"disliked_comments"`
|
||||
RealmIdentities []RealmMember `json:"identities"`
|
||||
Realms []Realm `json:"realms"`
|
||||
ExternalID uint `json:"external_id"`
|
||||
|
41
pkg/models/articles.go
Normal file
41
pkg/models/articles.go
Normal file
@ -0,0 +1,41 @@
|
||||
package models
|
||||
|
||||
type Article struct {
|
||||
PostBase
|
||||
|
||||
Title string `json:"title"`
|
||||
Hashtags []Tag `json:"tags" gorm:"many2many:article_tags"`
|
||||
Categories []Category `json:"categories" gorm:"many2many:article_categories"`
|
||||
LikedAccounts []ArticleLike `json:"liked_accounts"`
|
||||
DislikedAccounts []ArticleDislike `json:"disliked_accounts"`
|
||||
Description string `json:"description"`
|
||||
Content string `json:"content"`
|
||||
RealmID *uint `json:"realm_id"`
|
||||
Realm *Realm `json:"realm"`
|
||||
|
||||
Comments []Comment `json:"comments" gorm:"foreignKey:ArticleID"`
|
||||
}
|
||||
|
||||
func (p Article) GetReplyTo() PostInterface {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p Article) GetRepostTo() PostInterface {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p Article) GetHashtags() []Tag {
|
||||
return p.Hashtags
|
||||
}
|
||||
|
||||
func (p Article) GetCategories() []Category {
|
||||
return p.Categories
|
||||
}
|
||||
|
||||
func (p Article) SetHashtags(tags []Tag) {
|
||||
p.Hashtags = tags
|
||||
}
|
||||
|
||||
func (p Article) SetCategories(categories []Category) {
|
||||
p.Categories = categories
|
||||
}
|
@ -6,6 +6,15 @@ import (
|
||||
"path/filepath"
|
||||
)
|
||||
|
||||
type AttachmentType = uint8
|
||||
|
||||
const (
|
||||
AttachmentOthers = AttachmentType(iota)
|
||||
AttachmentPhoto
|
||||
AttachmentVideo
|
||||
AttachmentAudio
|
||||
)
|
||||
|
||||
type Attachment struct {
|
||||
BaseModel
|
||||
|
||||
@ -13,10 +22,12 @@ type Attachment struct {
|
||||
Filesize int64 `json:"filesize"`
|
||||
Filename string `json:"filename"`
|
||||
Mimetype string `json:"mimetype"`
|
||||
Type AttachmentType `json:"type"`
|
||||
ExternalUrl string `json:"external_url"`
|
||||
Post *Post `json:"post"`
|
||||
Author Account `json:"author"`
|
||||
PostID *uint `json:"post_id"`
|
||||
ArticleID *uint `json:"article_id"`
|
||||
MomentID *uint `json:"moment_id"`
|
||||
CommentID *uint `json:"comment_id"`
|
||||
AuthorID uint `json:"author_id"`
|
||||
}
|
||||
|
||||
|
@ -6,7 +6,9 @@ type Tag struct {
|
||||
Alias string `json:"alias" gorm:"uniqueIndex" validate:"lowercase,alphanum,min=4,max=24"`
|
||||
Name string `json:"name"`
|
||||
Description string `json:"description"`
|
||||
Posts []Post `json:"posts" gorm:"many2many:post_tags"`
|
||||
Articles []Article `json:"articles" gorm:"many2many:article_tags"`
|
||||
Moments []Moment `json:"moments" gorm:"many2many:moment_tags"`
|
||||
Comments []Comment `json:"comments" gorm:"many2many:comment_tags"`
|
||||
}
|
||||
|
||||
type Category struct {
|
||||
@ -15,5 +17,7 @@ type Category struct {
|
||||
Alias string `json:"alias" gorm:"uniqueIndex" validate:"lowercase,alphanum,min=4,max=24"`
|
||||
Name string `json:"name"`
|
||||
Description string `json:"description"`
|
||||
Posts []Post `json:"categories" gorm:"many2many:post_categories"`
|
||||
Articles []Article `json:"articles" gorm:"many2many:article_categories"`
|
||||
Moments []Moment `json:"moments" gorm:"many2many:moment_categories"`
|
||||
Comments []Comment `json:"comments" gorm:"many2many:comment_categories"`
|
||||
}
|
||||
|
38
pkg/models/comments.go
Normal file
38
pkg/models/comments.go
Normal file
@ -0,0 +1,38 @@
|
||||
package models
|
||||
|
||||
type Comment struct {
|
||||
PostBase
|
||||
|
||||
Content string `json:"content"`
|
||||
Hashtags []Tag `json:"tags" gorm:"many2many:comment_tags"`
|
||||
Categories []Category `json:"categories" gorm:"many2many:comment_categories"`
|
||||
LikedAccounts []CommentLike `json:"liked_accounts"`
|
||||
DislikedAccounts []CommentDislike `json:"disliked_accounts"`
|
||||
ReplyID *uint `json:"reply_id"`
|
||||
ReplyTo *Comment `json:"reply_to" gorm:"foreignKey:ReplyID"`
|
||||
|
||||
ArticleID *uint `json:"article_id"`
|
||||
MomentID *uint `json:"moment_id"`
|
||||
Article *Article `json:"article"`
|
||||
Moment *Moment `json:"moment"`
|
||||
}
|
||||
|
||||
func (p Comment) GetReplyTo() PostInterface {
|
||||
return p.ReplyTo
|
||||
}
|
||||
|
||||
func (p Comment) GetHashtags() []Tag {
|
||||
return p.Hashtags
|
||||
}
|
||||
|
||||
func (p Comment) GetCategories() []Category {
|
||||
return p.Categories
|
||||
}
|
||||
|
||||
func (p Comment) SetHashtags(tags []Tag) {
|
||||
p.Hashtags = tags
|
||||
}
|
||||
|
||||
func (p Comment) SetCategories(categories []Category) {
|
||||
p.Categories = categories
|
||||
}
|
41
pkg/models/moments.go
Normal file
41
pkg/models/moments.go
Normal file
@ -0,0 +1,41 @@
|
||||
package models
|
||||
|
||||
type Moment struct {
|
||||
PostBase
|
||||
|
||||
Content string `json:"content"`
|
||||
Hashtags []Tag `json:"tags" gorm:"many2many:moment_tags"`
|
||||
Categories []Category `json:"categories" gorm:"many2many:moment_categories"`
|
||||
LikedAccounts []MomentLike `json:"liked_accounts"`
|
||||
DislikedAccounts []MomentDislike `json:"disliked_accounts"`
|
||||
RealmID *uint `json:"realm_id"`
|
||||
RepostID *uint `json:"repost_id"`
|
||||
Realm *Realm `json:"realm"`
|
||||
RepostTo *Moment `json:"repost_to" gorm:"foreignKey:RepostID"`
|
||||
|
||||
Comments []Comment `json:"comments" gorm:"foreignKey:MomentID"`
|
||||
}
|
||||
|
||||
func (p Moment) GetRepostTo() PostInterface {
|
||||
return p.RepostTo
|
||||
}
|
||||
|
||||
func (p Moment) GetRealm() *Realm {
|
||||
return p.Realm
|
||||
}
|
||||
|
||||
func (p Moment) GetHashtags() []Tag {
|
||||
return p.Hashtags
|
||||
}
|
||||
|
||||
func (p Moment) GetCategories() []Category {
|
||||
return p.Categories
|
||||
}
|
||||
|
||||
func (p Moment) SetHashtags(tags []Tag) {
|
||||
p.Hashtags = tags
|
||||
}
|
||||
|
||||
func (p Moment) SetCategories(categories []Category) {
|
||||
p.Categories = categories
|
||||
}
|
@ -1,24 +1,24 @@
|
||||
package models
|
||||
|
||||
import "time"
|
||||
import (
|
||||
"time"
|
||||
)
|
||||
|
||||
type Post struct {
|
||||
type PostReactInfo struct {
|
||||
PostID uint `json:"post_id"`
|
||||
LikeCount int64 `json:"like_count"`
|
||||
DislikeCount int64 `json:"dislike_count"`
|
||||
ReplyCount int64 `json:"reply_count"`
|
||||
RepostCount int64 `json:"repost_count"`
|
||||
}
|
||||
|
||||
type PostBase struct {
|
||||
BaseModel
|
||||
|
||||
Content string `json:"content"`
|
||||
Hashtags []Tag `json:"tags" gorm:"many2many:post_tags"`
|
||||
Categories []Category `json:"categories" gorm:"many2many:post_categories"`
|
||||
Alias string `json:"alias" gorm:"uniqueIndex"`
|
||||
Attachments []Attachment `json:"attachments"`
|
||||
LikedAccounts []PostLike `json:"liked_accounts"`
|
||||
DislikedAccounts []PostDislike `json:"disliked_accounts"`
|
||||
RepostTo *Post `json:"repost_to" gorm:"foreignKey:RepostID"`
|
||||
ReplyTo *Post `json:"reply_to" gorm:"foreignKey:ReplyID"`
|
||||
PinnedAt *time.Time `json:"pinned_at"`
|
||||
EditedAt *time.Time `json:"edited_at"`
|
||||
PublishedAt time.Time `json:"published_at"`
|
||||
RepostID *uint `json:"repost_id"`
|
||||
ReplyID *uint `json:"reply_id"`
|
||||
RealmID *uint `json:"realm_id"`
|
||||
PublishedAt *time.Time `json:"published_at"`
|
||||
|
||||
AuthorID uint `json:"author_id"`
|
||||
Author Account `json:"author"`
|
||||
|
||||
@ -28,3 +28,44 @@ type Post struct {
|
||||
ReplyCount int64 `json:"reply_count" gorm:"-"`
|
||||
RepostCount int64 `json:"repost_count" gorm:"-"`
|
||||
}
|
||||
|
||||
func (p PostBase) GetID() uint {
|
||||
return p.ID
|
||||
}
|
||||
|
||||
func (p PostBase) GetReplyTo() PostInterface {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p PostBase) GetRepostTo() PostInterface {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p PostBase) GetAuthor() Account {
|
||||
return p.Author
|
||||
}
|
||||
|
||||
func (p PostBase) GetRealm() *Realm {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p PostBase) SetReactInfo(info PostReactInfo) {
|
||||
p.LikeCount = info.LikeCount
|
||||
p.DislikeCount = info.DislikeCount
|
||||
p.ReplyCount = info.ReplyCount
|
||||
p.RepostCount = info.RepostCount
|
||||
}
|
||||
|
||||
type PostInterface interface {
|
||||
GetID() uint
|
||||
GetHashtags() []Tag
|
||||
GetCategories() []Category
|
||||
GetReplyTo() PostInterface
|
||||
GetRepostTo() PostInterface
|
||||
GetAuthor() Account
|
||||
GetRealm() *Realm
|
||||
|
||||
SetHashtags([]Tag)
|
||||
SetCategories([]Category)
|
||||
SetReactInfo(PostReactInfo)
|
||||
}
|
||||
|
@ -2,18 +2,62 @@ package models
|
||||
|
||||
import "time"
|
||||
|
||||
type PostLike struct {
|
||||
type CommentLike struct {
|
||||
ID uint `json:"id" gorm:"primaryKey"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
UpdatedAt time.Time `json:"updated_at"`
|
||||
PostID uint `json:"post_id"`
|
||||
ArticleID *uint `json:"article_id"`
|
||||
MomentID *uint `json:"moment_id"`
|
||||
CommentID *uint `json:"comment_id"`
|
||||
AccountID uint `json:"account_id"`
|
||||
}
|
||||
|
||||
type PostDislike struct {
|
||||
type CommentDislike struct {
|
||||
ID uint `json:"id" gorm:"primaryKey"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
UpdatedAt time.Time `json:"updated_at"`
|
||||
PostID uint `json:"post_id"`
|
||||
ArticleID *uint `json:"article_id"`
|
||||
MomentID *uint `json:"moment_id"`
|
||||
CommentID *uint `json:"comment_id"`
|
||||
AccountID uint `json:"account_id"`
|
||||
}
|
||||
|
||||
type MomentLike struct {
|
||||
ID uint `json:"id" gorm:"primaryKey"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
UpdatedAt time.Time `json:"updated_at"`
|
||||
ArticleID *uint `json:"article_id"`
|
||||
MomentID *uint `json:"moment_id"`
|
||||
CommentID *uint `json:"comment_id"`
|
||||
AccountID uint `json:"account_id"`
|
||||
}
|
||||
|
||||
type MomentDislike struct {
|
||||
ID uint `json:"id" gorm:"primaryKey"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
UpdatedAt time.Time `json:"updated_at"`
|
||||
ArticleID *uint `json:"article_id"`
|
||||
MomentID *uint `json:"moment_id"`
|
||||
CommentID *uint `json:"comment_id"`
|
||||
AccountID uint `json:"account_id"`
|
||||
}
|
||||
|
||||
type ArticleLike struct {
|
||||
ID uint `json:"id" gorm:"primaryKey"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
UpdatedAt time.Time `json:"updated_at"`
|
||||
ArticleID *uint `json:"article_id"`
|
||||
MomentID *uint `json:"moment_id"`
|
||||
CommentID *uint `json:"comment_id"`
|
||||
AccountID uint `json:"account_id"`
|
||||
}
|
||||
|
||||
type ArticleDislike struct {
|
||||
ID uint `json:"id" gorm:"primaryKey"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
UpdatedAt time.Time `json:"updated_at"`
|
||||
ArticleID *uint `json:"article_id"`
|
||||
MomentID *uint `json:"moment_id"`
|
||||
CommentID *uint `json:"comment_id"`
|
||||
AccountID uint `json:"account_id"`
|
||||
}
|
||||
|
@ -5,7 +5,8 @@ type Realm struct {
|
||||
|
||||
Name string `json:"name"`
|
||||
Description string `json:"description"`
|
||||
Posts []Post `json:"posts"`
|
||||
Articles []Article `json:"article"`
|
||||
Moments []Moment `json:"moments"`
|
||||
Members []RealmMember `json:"members"`
|
||||
IsPublic bool `json:"is_public"`
|
||||
AccountID uint `json:"account_id"`
|
||||
|
@ -6,7 +6,7 @@ import (
|
||||
"github.com/gofiber/fiber/v2"
|
||||
)
|
||||
|
||||
func listCategroies(c *fiber.Ctx) error {
|
||||
func listCategories(c *fiber.Ctx) error {
|
||||
categories, err := services.ListCategory()
|
||||
if err != nil {
|
||||
return fiber.NewError(fiber.StatusNotFound, err.Error())
|
||||
|
@ -1,93 +0,0 @@
|
||||
package server
|
||||
|
||||
import (
|
||||
"code.smartsheep.studio/hydrogen/interactive/pkg/database"
|
||||
"code.smartsheep.studio/hydrogen/interactive/pkg/models"
|
||||
"code.smartsheep.studio/hydrogen/interactive/pkg/services"
|
||||
"github.com/gofiber/fiber/v2"
|
||||
"github.com/samber/lo"
|
||||
"time"
|
||||
)
|
||||
|
||||
func getOwnPost(c *fiber.Ctx) error {
|
||||
user := c.Locals("principal").(models.Account)
|
||||
|
||||
id, _ := c.ParamsInt("postId", 0)
|
||||
take := c.QueryInt("take", 0)
|
||||
offset := c.QueryInt("offset", 0)
|
||||
|
||||
tx := database.C.Where(&models.Post{
|
||||
BaseModel: models.BaseModel{ID: uint(id)},
|
||||
AuthorID: user.ID,
|
||||
})
|
||||
|
||||
post, err := services.GetPost(tx)
|
||||
if err != nil {
|
||||
return fiber.NewError(fiber.StatusNotFound, err.Error())
|
||||
}
|
||||
|
||||
tx = database.C.
|
||||
Where(&models.Post{ReplyID: &post.ID}).
|
||||
Where("published_at <= ? OR published_at IS NULL", time.Now()).
|
||||
Order("created_at desc")
|
||||
|
||||
var count int64
|
||||
if err := tx.
|
||||
Model(&models.Post{}).
|
||||
Count(&count).Error; err != nil {
|
||||
return fiber.NewError(fiber.StatusInternalServerError, err.Error())
|
||||
}
|
||||
|
||||
posts, err := services.ListPost(tx, take, offset)
|
||||
if err != nil {
|
||||
return fiber.NewError(fiber.StatusBadRequest, err.Error())
|
||||
}
|
||||
|
||||
return c.JSON(fiber.Map{
|
||||
"data": post,
|
||||
"count": count,
|
||||
"related": posts,
|
||||
})
|
||||
}
|
||||
|
||||
func listOwnPost(c *fiber.Ctx) error {
|
||||
take := c.QueryInt("take", 0)
|
||||
offset := c.QueryInt("offset", 0)
|
||||
realmId := c.QueryInt("realmId", 0)
|
||||
|
||||
user := c.Locals("principal").(models.Account)
|
||||
|
||||
tx := database.C.
|
||||
Where(&models.Post{AuthorID: user.ID}).
|
||||
Where("published_at <= ? OR published_at IS NULL", time.Now()).
|
||||
Order("created_at desc")
|
||||
|
||||
if realmId > 0 {
|
||||
tx = tx.Where(&models.Post{RealmID: lo.ToPtr(uint(realmId))})
|
||||
}
|
||||
|
||||
if len(c.Query("category")) > 0 {
|
||||
tx = services.FilterPostWithCategory(tx, c.Query("category"))
|
||||
}
|
||||
|
||||
if len(c.Query("tag")) > 0 {
|
||||
tx = services.FilterPostWithTag(tx, c.Query("tag"))
|
||||
}
|
||||
|
||||
var count int64
|
||||
if err := tx.
|
||||
Model(&models.Post{}).
|
||||
Count(&count).Error; err != nil {
|
||||
return fiber.NewError(fiber.StatusInternalServerError, err.Error())
|
||||
}
|
||||
|
||||
posts, err := services.ListPost(tx, take, offset)
|
||||
if err != nil {
|
||||
return fiber.NewError(fiber.StatusBadRequest, err.Error())
|
||||
}
|
||||
|
||||
return c.JSON(fiber.Map{
|
||||
"count": count,
|
||||
"data": posts,
|
||||
})
|
||||
}
|
@ -12,105 +12,81 @@ import (
|
||||
"github.com/samber/lo"
|
||||
)
|
||||
|
||||
func getPost(c *fiber.Ctx) error {
|
||||
id, _ := c.ParamsInt("postId", 0)
|
||||
take := c.QueryInt("take", 0)
|
||||
offset := c.QueryInt("offset", 0)
|
||||
func getMomentContext() *services.PostTypeContext[models.Moment] {
|
||||
return &services.PostTypeContext[models.Moment]{
|
||||
Tx: database.C,
|
||||
TypeName: "Moment",
|
||||
CanReply: false,
|
||||
CanRepost: true,
|
||||
}
|
||||
}
|
||||
|
||||
tx := database.C.Where(&models.Post{
|
||||
BaseModel: models.BaseModel{ID: uint(id)},
|
||||
}).Where("published_at <= ? OR published_at IS NULL", time.Now())
|
||||
func getMoment(c *fiber.Ctx) error {
|
||||
id, _ := c.ParamsInt("momentId", 0)
|
||||
|
||||
post, err := services.GetPost(tx)
|
||||
mx := getMomentContext().FilterPublishedAt(time.Now())
|
||||
|
||||
item, err := mx.Get(uint(id))
|
||||
if err != nil {
|
||||
return fiber.NewError(fiber.StatusNotFound, err.Error())
|
||||
}
|
||||
|
||||
tx = database.C.
|
||||
Where(&models.Post{ReplyID: &post.ID}).
|
||||
Where("published_at <= ? OR published_at IS NULL", time.Now()).
|
||||
Order("created_at desc")
|
||||
|
||||
var count int64
|
||||
if err := tx.
|
||||
Model(&models.Post{}).
|
||||
Count(&count).Error; err != nil {
|
||||
return fiber.NewError(fiber.StatusInternalServerError, err.Error())
|
||||
}
|
||||
|
||||
posts, err := services.ListPost(tx, take, offset)
|
||||
if err != nil {
|
||||
return fiber.NewError(fiber.StatusBadRequest, err.Error())
|
||||
}
|
||||
|
||||
return c.JSON(fiber.Map{
|
||||
"data": post,
|
||||
"count": count,
|
||||
"related": posts,
|
||||
})
|
||||
return c.JSON(item)
|
||||
}
|
||||
|
||||
func listPost(c *fiber.Ctx) error {
|
||||
func listMoment(c *fiber.Ctx) error {
|
||||
take := c.QueryInt("take", 0)
|
||||
offset := c.QueryInt("offset", 0)
|
||||
|
||||
realmId := c.QueryInt("realmId", 0)
|
||||
|
||||
tx := database.C.
|
||||
Where("published_at <= ? OR published_at IS NULL", time.Now()).
|
||||
Order("created_at desc")
|
||||
|
||||
if realmId > 0 {
|
||||
tx = tx.Where(&models.Post{RealmID: lo.ToPtr(uint(realmId))})
|
||||
} else {
|
||||
tx = tx.Where("realm_id IS NULL")
|
||||
}
|
||||
mx := getMomentContext().
|
||||
FilterPublishedAt(time.Now()).
|
||||
FilterRealm(uint(realmId)).
|
||||
SortCreatedAt("desc")
|
||||
|
||||
var author models.Account
|
||||
if len(c.Query("authorId")) > 0 {
|
||||
if err := database.C.Where(&models.Account{Name: c.Query("authorId")}).First(&author).Error; err != nil {
|
||||
return fiber.NewError(fiber.StatusNotFound, err.Error())
|
||||
}
|
||||
tx = tx.Where(&models.Post{AuthorID: author.ID})
|
||||
mx = mx.FilterAuthor(author.ID)
|
||||
}
|
||||
|
||||
if len(c.Query("category")) > 0 {
|
||||
tx = services.FilterPostWithCategory(tx, c.Query("category"))
|
||||
mx = mx.FilterWithCategory(c.Query("category"))
|
||||
}
|
||||
if len(c.Query("tag")) > 0 {
|
||||
tx = services.FilterPostWithTag(tx, c.Query("tag"))
|
||||
mx = mx.FilterWithTag(c.Query("tag"))
|
||||
}
|
||||
|
||||
if !c.QueryBool("reply", true) {
|
||||
tx = tx.Where("reply_id IS NULL")
|
||||
mx = mx.FilterReply(true)
|
||||
}
|
||||
|
||||
var count int64
|
||||
if err := tx.
|
||||
Model(&models.Post{}).
|
||||
Count(&count).Error; err != nil {
|
||||
count, err := mx.Count()
|
||||
if err != nil {
|
||||
return fiber.NewError(fiber.StatusInternalServerError, err.Error())
|
||||
}
|
||||
|
||||
posts, err := services.ListPost(tx, take, offset)
|
||||
items, err := mx.List(take, offset)
|
||||
if err != nil {
|
||||
return fiber.NewError(fiber.StatusBadRequest, err.Error())
|
||||
}
|
||||
|
||||
return c.JSON(fiber.Map{
|
||||
"count": count,
|
||||
"data": posts,
|
||||
"data": items,
|
||||
})
|
||||
}
|
||||
|
||||
func createPost(c *fiber.Ctx) error {
|
||||
func createMoment(c *fiber.Ctx) error {
|
||||
user := c.Locals("principal").(models.Account)
|
||||
|
||||
var data struct {
|
||||
Alias string `json:"alias"`
|
||||
Title string `json:"title"`
|
||||
Content string `json:"content" validate:"required"`
|
||||
Tags []models.Tag `json:"tags"`
|
||||
Hashtags []models.Tag `json:"hashtags"`
|
||||
Categories []models.Category `json:"categories"`
|
||||
Attachments []models.Attachment `json:"attachments"`
|
||||
PublishedAt *time.Time `json:"published_at"`
|
||||
@ -125,28 +101,30 @@ func createPost(c *fiber.Ctx) error {
|
||||
data.Alias = strings.ReplaceAll(uuid.NewString(), "-", "")
|
||||
}
|
||||
|
||||
var repostTo *uint = nil
|
||||
var replyTo *uint = nil
|
||||
mx := getMomentContext()
|
||||
|
||||
item := models.Moment{
|
||||
PostBase: models.PostBase{
|
||||
Alias: data.Alias,
|
||||
Attachments: data.Attachments,
|
||||
PublishedAt: data.PublishedAt,
|
||||
AuthorID: user.ID,
|
||||
},
|
||||
Hashtags: data.Hashtags,
|
||||
Categories: data.Categories,
|
||||
Content: data.Content,
|
||||
RealmID: data.RealmID,
|
||||
}
|
||||
|
||||
var relatedCount int64
|
||||
if data.RepostTo > 0 {
|
||||
if err := database.C.Where(&models.Post{
|
||||
BaseModel: models.BaseModel{ID: data.RepostTo},
|
||||
}).Model(&models.Post{}).Count(&relatedCount).Error; err != nil {
|
||||
if err := database.C.Where("id = ?", data.RepostTo).
|
||||
Model(&models.Moment{}).Count(&relatedCount).Error; err != nil {
|
||||
return fiber.NewError(fiber.StatusBadRequest, err.Error())
|
||||
} else if relatedCount <= 0 {
|
||||
return fiber.NewError(fiber.StatusNotFound, "related post was not found")
|
||||
} else {
|
||||
repostTo = &data.RepostTo
|
||||
}
|
||||
} else if data.ReplyTo > 0 {
|
||||
if err := database.C.Where(&models.Post{
|
||||
BaseModel: models.BaseModel{ID: data.ReplyTo},
|
||||
}).Model(&models.Post{}).Count(&relatedCount).Error; err != nil {
|
||||
return fiber.NewError(fiber.StatusBadRequest, err.Error())
|
||||
} else if relatedCount <= 0 {
|
||||
return fiber.NewError(fiber.StatusNotFound, "related post was not found")
|
||||
} else {
|
||||
replyTo = &data.ReplyTo
|
||||
item.RepostID = &data.RepostTo
|
||||
}
|
||||
}
|
||||
|
||||
@ -159,34 +137,23 @@ func createPost(c *fiber.Ctx) error {
|
||||
}
|
||||
}
|
||||
|
||||
post, err := services.NewPost(
|
||||
user,
|
||||
realm,
|
||||
data.Content,
|
||||
data.Attachments,
|
||||
data.Categories,
|
||||
data.Tags,
|
||||
data.PublishedAt,
|
||||
replyTo,
|
||||
repostTo,
|
||||
)
|
||||
item, err := mx.New(item)
|
||||
if err != nil {
|
||||
return fiber.NewError(fiber.StatusBadRequest, err.Error())
|
||||
}
|
||||
|
||||
return c.JSON(post)
|
||||
return c.JSON(item)
|
||||
}
|
||||
|
||||
func editPost(c *fiber.Ctx) error {
|
||||
func editMoment(c *fiber.Ctx) error {
|
||||
user := c.Locals("principal").(models.Account)
|
||||
id, _ := c.ParamsInt("postId", 0)
|
||||
id, _ := c.ParamsInt("momentId", 0)
|
||||
|
||||
var data struct {
|
||||
Alias string `json:"alias" validate:"required"`
|
||||
Title string `json:"title"`
|
||||
Content string `json:"content" validate:"required"`
|
||||
PublishedAt *time.Time `json:"published_at"`
|
||||
Tags []models.Tag `json:"tags"`
|
||||
Hashtags []models.Tag `json:"hashtags"`
|
||||
Categories []models.Category `json:"categories"`
|
||||
Attachments []models.Attachment `json:"attachments"`
|
||||
}
|
||||
@ -195,49 +162,48 @@ func editPost(c *fiber.Ctx) error {
|
||||
return err
|
||||
}
|
||||
|
||||
var post models.Post
|
||||
if err := database.C.Where(&models.Post{
|
||||
BaseModel: models.BaseModel{ID: uint(id)},
|
||||
AuthorID: user.ID,
|
||||
}).First(&post).Error; err != nil {
|
||||
mx := getMomentContext().FilterAuthor(user.ID)
|
||||
|
||||
item, err := mx.Get(uint(id), true)
|
||||
if err != nil {
|
||||
return fiber.NewError(fiber.StatusNotFound, err.Error())
|
||||
}
|
||||
|
||||
post, err := services.EditPost(
|
||||
post,
|
||||
data.Content,
|
||||
data.PublishedAt,
|
||||
data.Categories,
|
||||
data.Tags,
|
||||
data.Attachments,
|
||||
)
|
||||
item.Alias = data.Alias
|
||||
item.Content = data.Content
|
||||
item.PublishedAt = data.PublishedAt
|
||||
item.Hashtags = data.Hashtags
|
||||
item.Categories = data.Categories
|
||||
item.Attachments = data.Attachments
|
||||
|
||||
item, err = mx.Edit(item)
|
||||
if err != nil {
|
||||
return fiber.NewError(fiber.StatusBadRequest, err.Error())
|
||||
}
|
||||
|
||||
return c.JSON(post)
|
||||
return c.JSON(item)
|
||||
}
|
||||
|
||||
func reactPost(c *fiber.Ctx) error {
|
||||
func reactMoment(c *fiber.Ctx) error {
|
||||
user := c.Locals("principal").(models.Account)
|
||||
id, _ := c.ParamsInt("postId", 0)
|
||||
id, _ := c.ParamsInt("momentId", 0)
|
||||
|
||||
var post models.Post
|
||||
if err := database.C.Where(&models.Post{
|
||||
BaseModel: models.BaseModel{ID: uint(id)},
|
||||
}).First(&post).Error; err != nil {
|
||||
mx := getMomentContext()
|
||||
|
||||
item, err := mx.Get(uint(id), true)
|
||||
if err != nil {
|
||||
return fiber.NewError(fiber.StatusNotFound, err.Error())
|
||||
}
|
||||
|
||||
switch strings.ToLower(c.Params("reactType")) {
|
||||
case "like":
|
||||
if positive, err := services.LikePost(user, post); err != nil {
|
||||
if positive, err := mx.ReactLike(user, item.ID); err != nil {
|
||||
return fiber.NewError(fiber.StatusBadRequest, err.Error())
|
||||
} else {
|
||||
return c.SendStatus(lo.Ternary(positive, fiber.StatusCreated, fiber.StatusNoContent))
|
||||
}
|
||||
case "dislike":
|
||||
if positive, err := services.DislikePost(user, post); err != nil {
|
||||
if positive, err := mx.ReactDislike(user, item.ID); err != nil {
|
||||
return fiber.NewError(fiber.StatusBadRequest, err.Error())
|
||||
} else {
|
||||
return c.SendStatus(lo.Ternary(positive, fiber.StatusCreated, fiber.StatusNoContent))
|
||||
@ -247,19 +213,18 @@ func reactPost(c *fiber.Ctx) error {
|
||||
}
|
||||
}
|
||||
|
||||
func deletePost(c *fiber.Ctx) error {
|
||||
func deleteMoment(c *fiber.Ctx) error {
|
||||
user := c.Locals("principal").(models.Account)
|
||||
id, _ := c.ParamsInt("postId", 0)
|
||||
id, _ := c.ParamsInt("momentId", 0)
|
||||
|
||||
var post models.Post
|
||||
if err := database.C.Where(&models.Post{
|
||||
BaseModel: models.BaseModel{ID: uint(id)},
|
||||
AuthorID: user.ID,
|
||||
}).First(&post).Error; err != nil {
|
||||
mx := getMomentContext().FilterAuthor(user.ID)
|
||||
|
||||
item, err := mx.Get(uint(id), true)
|
||||
if err != nil {
|
||||
return fiber.NewError(fiber.StatusNotFound, err.Error())
|
||||
}
|
||||
|
||||
if err := services.DeletePost(post); err != nil {
|
||||
if err := mx.Delete(item); err != nil {
|
||||
return fiber.NewError(fiber.StatusBadRequest, err.Error())
|
||||
}
|
||||
|
@ -69,21 +69,23 @@ func NewServer() {
|
||||
}), openAttachment)
|
||||
api.Post("/attachments", authMiddleware, uploadAttachment)
|
||||
|
||||
api.Get("/posts", listPost)
|
||||
api.Get("/posts/:postId", getPost)
|
||||
api.Post("/posts", authMiddleware, createPost)
|
||||
api.Post("/posts/:postId/react/:reactType", authMiddleware, reactPost)
|
||||
api.Put("/posts/:postId", authMiddleware, editPost)
|
||||
api.Delete("/posts/:postId", authMiddleware, deletePost)
|
||||
// TODO Feed (aka. Union source)
|
||||
|
||||
api.Get("/categories", listCategroies)
|
||||
moments := api.Group("/moments").Name("Moments API")
|
||||
{
|
||||
moments.Get("/", listMoment)
|
||||
moments.Get("/:momentId", getMoment)
|
||||
moments.Post("/", authMiddleware, createMoment)
|
||||
moments.Post("/:momentId/react/:reactType", authMiddleware, reactMoment)
|
||||
moments.Put("/:momentId", authMiddleware, editMoment)
|
||||
moments.Delete("/:momentId", authMiddleware, deleteMoment)
|
||||
}
|
||||
|
||||
api.Get("/categories", listCategories)
|
||||
api.Post("/categories", authMiddleware, newCategory)
|
||||
api.Put("/categories/:categoryId", authMiddleware, editCategory)
|
||||
api.Delete("/categories/:categoryId", authMiddleware, deleteCategory)
|
||||
|
||||
api.Get("/creators/posts", authMiddleware, listOwnPost)
|
||||
api.Get("/creators/posts/:postId", authMiddleware, getOwnPost)
|
||||
|
||||
api.Get("/realms", listRealm)
|
||||
api.Get("/realms/me", authMiddleware, listOwnedRealm)
|
||||
api.Get("/realms/me/available", authMiddleware, listAvailableRealm)
|
||||
|
@ -14,7 +14,7 @@ func NewAttachment(user models.Account, header *multipart.FileHeader) (models.At
|
||||
Filesize: header.Size,
|
||||
Filename: header.Filename,
|
||||
Mimetype: "unknown/unknown",
|
||||
PostID: nil,
|
||||
Type: models.AttachmentOthers,
|
||||
AuthorID: user.ID,
|
||||
}
|
||||
|
||||
|
1
pkg/services/moments.go
Normal file
1
pkg/services/moments.go
Normal file
@ -0,0 +1 @@
|
||||
package services
|
@ -2,112 +2,191 @@ package services
|
||||
|
||||
import (
|
||||
"code.smartsheep.studio/hydrogen/identity/pkg/grpc/proto"
|
||||
"errors"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/rs/zerolog/log"
|
||||
|
||||
"code.smartsheep.studio/hydrogen/interactive/pkg/database"
|
||||
"code.smartsheep.studio/hydrogen/interactive/pkg/models"
|
||||
"fmt"
|
||||
pluralize "github.com/gertd/go-pluralize"
|
||||
"github.com/rs/zerolog/log"
|
||||
"github.com/samber/lo"
|
||||
"github.com/spf13/viper"
|
||||
"gorm.io/gorm"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
func PreloadRelatedPost(tx *gorm.DB) *gorm.DB {
|
||||
return tx.
|
||||
Preload("Author").
|
||||
Preload("Attachments").
|
||||
Preload("Categories").
|
||||
Preload("Hashtags").
|
||||
Preload("RepostTo").
|
||||
Preload("ReplyTo").
|
||||
Preload("RepostTo.Author").
|
||||
Preload("ReplyTo.Author").
|
||||
Preload("RepostTo.Attachments").
|
||||
Preload("ReplyTo.Attachments").
|
||||
Preload("RepostTo.Categories").
|
||||
Preload("ReplyTo.Categories").
|
||||
Preload("RepostTo.Hashtags").
|
||||
Preload("ReplyTo.Hashtags")
|
||||
}
|
||||
|
||||
func FilterPostWithCategory(tx *gorm.DB, alias string) *gorm.DB {
|
||||
prefix := viper.GetString("database.prefix")
|
||||
return tx.Joins(fmt.Sprintf("JOIN %spost_categories ON %sposts.id = %spost_categories.post_id", prefix, prefix, prefix)).
|
||||
Joins(fmt.Sprintf("JOIN %scategories ON %scategories.id = %spost_categories.category_id", prefix, prefix, prefix)).
|
||||
Where(fmt.Sprintf("%scategories.alias = ?", prefix), alias)
|
||||
}
|
||||
|
||||
func FilterPostWithTag(tx *gorm.DB, alias string) *gorm.DB {
|
||||
prefix := viper.GetString("database.prefix")
|
||||
return tx.Joins(fmt.Sprintf("JOIN %spost_tags ON %sposts.id = %spost_tags.post_id", prefix, prefix, prefix)).
|
||||
Joins(fmt.Sprintf("JOIN %stags ON %stags.id = %spost_tags.tag_id", prefix, prefix, prefix)).
|
||||
Where(fmt.Sprintf("%stags.alias = ?", prefix), alias)
|
||||
}
|
||||
|
||||
func GetPost(tx *gorm.DB) (*models.Post, error) {
|
||||
var post *models.Post
|
||||
if err := PreloadRelatedPost(tx).First(&post).Error; err != nil {
|
||||
return post, err
|
||||
}
|
||||
|
||||
var reactInfo struct {
|
||||
PostID uint `json:"post_id"`
|
||||
LikeCount int64 `json:"like_count"`
|
||||
DislikeCount int64 `json:"dislike_count"`
|
||||
ReplyCount int64 `json:"reply_count"`
|
||||
RepostCount int64 `json:"repost_count"`
|
||||
}
|
||||
|
||||
prefix := viper.GetString("database.prefix")
|
||||
database.C.Raw(fmt.Sprintf(`
|
||||
SELECT t.id as post_id,
|
||||
const (
|
||||
reactUnionSelect = `SELECT t.id AS post_id,
|
||||
COALESCE(l.like_count, 0) AS like_count,
|
||||
COALESCE(d.dislike_count, 0) AS dislike_count,
|
||||
COALESCE(r.reply_count, 0) AS reply_count,
|
||||
COALESCE(rp.repost_count, 0) AS repost_count
|
||||
FROM %sposts t
|
||||
LEFT JOIN (SELECT post_id, COUNT(*) AS like_count
|
||||
FROM %spost_likes
|
||||
GROUP BY post_id) l ON t.id = l.post_id
|
||||
LEFT JOIN (SELECT post_id, COUNT(*) AS dislike_count
|
||||
FROM %spost_dislikes
|
||||
GROUP BY post_id) d ON t.id = d.post_id
|
||||
LEFT JOIN (SELECT reply_id, COUNT(*) AS reply_count
|
||||
FROM %sposts
|
||||
COALESCE(d.dislike_count, 0) AS dislike_count--!COMMA!--
|
||||
--!REPLY_UNION_COLUMN!-- --!BOTH_COMMA!--
|
||||
--!REPOST_UNION_COLUMN!--
|
||||
FROM %s t
|
||||
LEFT JOIN (SELECT %s_id, COUNT(*) AS like_count
|
||||
FROM %s_likes
|
||||
GROUP BY %s_id) l ON t.id = l.%s_id
|
||||
LEFT JOIN (SELECT %s_id, COUNT(*) AS dislike_count
|
||||
FROM %s_likes
|
||||
GROUP BY %s_id) d ON t.id = d.%s_id
|
||||
--!REPLY_UNION_SELECT!--
|
||||
--!REPOST_UNION_SELECT!--
|
||||
WHERE t.id = ?`
|
||||
// TODO Solve for the cross table query(like articles -> comments)
|
||||
replyUnionColumn = `COALESCE(r.reply_count, 0) AS reply_count`
|
||||
replyUnionSelect = `LEFT JOIN (SELECT reply_id, COUNT(*) AS reply_count
|
||||
FROM %s
|
||||
WHERE reply_id IS NOT NULL
|
||||
GROUP BY reply_id) r ON t.id = r.reply_id
|
||||
LEFT JOIN (SELECT repost_id, COUNT(*) AS repost_count
|
||||
FROM %sposts
|
||||
GROUP BY reply_id) r ON t.id = r.reply_id`
|
||||
repostUnionColumn = `COALESCE(rp.repost_count, 0) AS repost_count`
|
||||
repostUnionSelect = `LEFT JOIN (SELECT repost_id, COUNT(*) AS repost_count
|
||||
FROM %s
|
||||
WHERE repost_id IS NOT NULL
|
||||
GROUP BY repost_id) rp ON t.id = rp.repost_id
|
||||
WHERE t.id = ?`, prefix, prefix, prefix, prefix, prefix), post.ID).Scan(&reactInfo)
|
||||
GROUP BY repost_id) rp ON t.id = rp.repost_id`
|
||||
)
|
||||
|
||||
post.LikeCount = reactInfo.LikeCount
|
||||
post.DislikeCount = reactInfo.DislikeCount
|
||||
post.ReplyCount = reactInfo.ReplyCount
|
||||
post.RepostCount = reactInfo.RepostCount
|
||||
type PostTypeContext[T models.PostInterface] struct {
|
||||
Tx *gorm.DB
|
||||
|
||||
return post, nil
|
||||
TypeName string
|
||||
CanReply bool
|
||||
CanRepost bool
|
||||
}
|
||||
|
||||
func ListPost(tx *gorm.DB, take int, offset int) ([]*models.Post, error) {
|
||||
var pluralizeHelper = pluralize.NewClient()
|
||||
|
||||
func (v *PostTypeContext[T]) GetTableName(plural ...bool) string {
|
||||
if len(plural) <= 0 || !plural[0] {
|
||||
return strings.ToLower(v.TypeName)
|
||||
} else {
|
||||
return pluralizeHelper.Plural(strings.ToLower(v.TypeName))
|
||||
}
|
||||
}
|
||||
|
||||
func (v *PostTypeContext[T]) Preload() *PostTypeContext[T] {
|
||||
v.Tx.Preload("Author").Preload("Attachments").Preload("Categories").Preload("Hashtags")
|
||||
|
||||
if v.CanReply {
|
||||
v.Tx.Preload("ReplyTo")
|
||||
}
|
||||
if v.CanRepost {
|
||||
v.Tx.Preload("RepostTo")
|
||||
}
|
||||
|
||||
return v
|
||||
}
|
||||
|
||||
func (v *PostTypeContext[T]) FilterWithCategory(alias string) *PostTypeContext[T] {
|
||||
table := v.GetTableName()
|
||||
v.Tx.Joins(fmt.Sprintf("JOIN %s_categories ON %s.id = %s_categories.%s_id", table, v.GetTableName(true), table, v.GetTableName())).
|
||||
Joins(fmt.Sprintf("JOIN %s_categories ON %s_categories.id = %s_categories.category_id", table, table, table)).
|
||||
Where(table+"_categories.alias = ?", alias)
|
||||
return v
|
||||
}
|
||||
|
||||
func (v *PostTypeContext[T]) FilterWithTag(alias string) *PostTypeContext[T] {
|
||||
table := v.GetTableName()
|
||||
v.Tx.Joins(fmt.Sprintf("JOIN %s_tags ON %s.id = %s_tags.%s_id", table, v.GetTableName(true), table, v.GetTableName())).
|
||||
Joins(fmt.Sprintf("JOIN %s_tags ON %s_tags.id = %s_tags.category_id", table, table, table)).
|
||||
Where(table+"_tags.alias = ?", alias)
|
||||
return v
|
||||
}
|
||||
|
||||
func (v *PostTypeContext[T]) FilterPublishedAt(date time.Time) *PostTypeContext[T] {
|
||||
v.Tx.Where("published_at <= ? AND published_at IS NULL", date)
|
||||
return v
|
||||
}
|
||||
|
||||
func (v *PostTypeContext[T]) FilterRealm(id uint) *PostTypeContext[T] {
|
||||
if id > 0 {
|
||||
v.Tx = v.Tx.Where("realm_id = ?", id)
|
||||
} else {
|
||||
v.Tx = v.Tx.Where("realm_id IS NULL")
|
||||
}
|
||||
return v
|
||||
}
|
||||
|
||||
func (v *PostTypeContext[T]) FilterAuthor(id uint) *PostTypeContext[T] {
|
||||
v.Tx = v.Tx.Where("author_id = ?", id)
|
||||
return v
|
||||
}
|
||||
|
||||
func (v *PostTypeContext[T]) FilterReply(condition bool) *PostTypeContext[T] {
|
||||
if condition {
|
||||
v.Tx = v.Tx.Where("reply_id IS NOT NULL")
|
||||
} else {
|
||||
v.Tx = v.Tx.Where("reply_id IS NULL")
|
||||
}
|
||||
return v
|
||||
}
|
||||
|
||||
func (v *PostTypeContext[T]) SortCreatedAt(order string) *PostTypeContext[T] {
|
||||
v.Tx.Order(fmt.Sprintf("created_at %s", order))
|
||||
return v
|
||||
}
|
||||
|
||||
func (v *PostTypeContext[T]) BuildReactInfoSql() string {
|
||||
column := strings.ToLower(v.TypeName)
|
||||
table := viper.GetString("database.prefix") + v.GetTableName()
|
||||
pluralTable := viper.GetString("database.prefix") + v.GetTableName(true)
|
||||
sql := fmt.Sprintf(reactUnionSelect, pluralTable, column, table, column, column, column, table, column, column)
|
||||
|
||||
if v.CanReply {
|
||||
sql = strings.Replace(sql, "--!REPLY_UNION_COLUMN!--", replyUnionColumn, 1)
|
||||
sql = strings.Replace(sql, "--!REPLY_UNION_SELECT!--", fmt.Sprintf(replyUnionSelect, pluralTable), 1)
|
||||
}
|
||||
if v.CanRepost {
|
||||
sql = strings.Replace(sql, "--!REPOST_UNION_COLUMN!--", repostUnionColumn, 1)
|
||||
sql = strings.Replace(sql, "--!REPOST_UNION_SELECT!--", fmt.Sprintf(repostUnionSelect, pluralTable), 1)
|
||||
}
|
||||
if v.CanReply || v.CanRepost {
|
||||
sql = strings.ReplaceAll(sql, "--!COMMA!--", ",")
|
||||
}
|
||||
if v.CanReply && v.CanRepost {
|
||||
sql = strings.ReplaceAll(sql, "--!BOTH_COMMA!--", ",")
|
||||
}
|
||||
|
||||
return sql
|
||||
}
|
||||
|
||||
func (v *PostTypeContext[T]) Get(id uint, noReact ...bool) (T, error) {
|
||||
var item T
|
||||
if err := v.Preload().Tx.Where("id = ?", id).First(&item).Error; err != nil {
|
||||
return item, err
|
||||
}
|
||||
|
||||
var reactInfo models.PostReactInfo
|
||||
|
||||
if len(noReact) <= 0 || !noReact[0] {
|
||||
sql := v.BuildReactInfoSql()
|
||||
database.C.Raw(sql, item.GetID()).Scan(&reactInfo)
|
||||
}
|
||||
|
||||
item.SetReactInfo(reactInfo)
|
||||
|
||||
return item, nil
|
||||
}
|
||||
|
||||
func (v *PostTypeContext[T]) Count() (int64, error) {
|
||||
var count int64
|
||||
table := viper.GetString("database.prefix") + v.GetTableName(true)
|
||||
if err := v.Tx.Table(table).Count(&count).Error; err != nil {
|
||||
return count, err
|
||||
}
|
||||
|
||||
return count, nil
|
||||
}
|
||||
|
||||
func (v *PostTypeContext[T]) List(take int, offset int, noReact ...bool) ([]T, error) {
|
||||
if take > 20 {
|
||||
take = 20
|
||||
}
|
||||
|
||||
var posts []*models.Post
|
||||
if err := PreloadRelatedPost(tx).
|
||||
Limit(take).
|
||||
Offset(offset).
|
||||
Find(&posts).Error; err != nil {
|
||||
return posts, err
|
||||
var items []T
|
||||
if err := v.Preload().Tx.Limit(take).Offset(offset).Find(&items).Error; err != nil {
|
||||
return items, err
|
||||
}
|
||||
|
||||
postIds := lo.Map(posts, func(item *models.Post, _ int) uint {
|
||||
return item.ID
|
||||
idx := lo.Map(items, func(item T, _ int) uint {
|
||||
return item.GetID()
|
||||
})
|
||||
|
||||
var reactInfo []struct {
|
||||
@ -118,116 +197,77 @@ func ListPost(tx *gorm.DB, take int, offset int) ([]*models.Post, error) {
|
||||
RepostCount int64 `json:"repost_count"`
|
||||
}
|
||||
|
||||
prefix := viper.GetString("database.prefix")
|
||||
database.C.Raw(fmt.Sprintf(`
|
||||
SELECT t.id as post_id,
|
||||
COALESCE(l.like_count, 0) AS like_count,
|
||||
COALESCE(d.dislike_count, 0) AS dislike_count,
|
||||
COALESCE(r.reply_count, 0) AS reply_count,
|
||||
COALESCE(rp.repost_count, 0) AS repost_count
|
||||
FROM %sposts t
|
||||
LEFT JOIN (SELECT post_id, COUNT(*) AS like_count
|
||||
FROM %spost_likes
|
||||
GROUP BY post_id) l ON t.id = l.post_id
|
||||
LEFT JOIN (SELECT post_id, COUNT(*) AS dislike_count
|
||||
FROM %spost_dislikes
|
||||
GROUP BY post_id) d ON t.id = d.post_id
|
||||
LEFT JOIN (SELECT reply_id, COUNT(*) AS reply_count
|
||||
FROM %sposts
|
||||
WHERE reply_id IS NOT NULL
|
||||
GROUP BY reply_id) r ON t.id = r.reply_id
|
||||
LEFT JOIN (SELECT repost_id, COUNT(*) AS repost_count
|
||||
FROM %sposts
|
||||
WHERE repost_id IS NOT NULL
|
||||
GROUP BY repost_id) rp ON t.id = rp.repost_id
|
||||
WHERE t.id IN ?`, prefix, prefix, prefix, prefix, prefix), postIds).Scan(&reactInfo)
|
||||
if len(noReact) <= 0 || !noReact[0] {
|
||||
sql := v.BuildReactInfoSql()
|
||||
database.C.Raw(sql, idx).Scan(&reactInfo)
|
||||
}
|
||||
|
||||
postMap := lo.SliceToMap(posts, func(item *models.Post) (uint, *models.Post) {
|
||||
return item.ID, item
|
||||
itemMap := lo.SliceToMap(items, func(item T) (uint, T) {
|
||||
return item.GetID(), item
|
||||
})
|
||||
|
||||
for _, info := range reactInfo {
|
||||
if post, ok := postMap[info.PostID]; ok {
|
||||
post.LikeCount = info.LikeCount
|
||||
post.DislikeCount = info.DislikeCount
|
||||
post.ReplyCount = info.ReplyCount
|
||||
post.RepostCount = info.RepostCount
|
||||
if item, ok := itemMap[info.PostID]; ok {
|
||||
item.SetReactInfo(info)
|
||||
}
|
||||
}
|
||||
|
||||
return posts, nil
|
||||
return items, nil
|
||||
}
|
||||
|
||||
func NewPost(
|
||||
user models.Account,
|
||||
realm *models.Realm,
|
||||
content string,
|
||||
attachments []models.Attachment,
|
||||
categories []models.Category,
|
||||
tags []models.Tag,
|
||||
publishedAt *time.Time,
|
||||
replyTo, repostTo *uint,
|
||||
) (models.Post, error) {
|
||||
func (v *PostTypeContext[T]) MapCategoriesAndTags(item T) (T, error) {
|
||||
var err error
|
||||
var post models.Post
|
||||
categories := item.GetCategories()
|
||||
for idx, category := range categories {
|
||||
categories[idx], err = GetCategory(category.Alias)
|
||||
if err != nil {
|
||||
return post, err
|
||||
return item, err
|
||||
}
|
||||
}
|
||||
item.SetCategories(categories)
|
||||
tags := item.GetHashtags()
|
||||
for idx, tag := range tags {
|
||||
tags[idx], err = GetTagOrCreate(tag.Alias, tag.Name)
|
||||
if err != nil {
|
||||
return post, err
|
||||
return item, err
|
||||
}
|
||||
}
|
||||
item.SetHashtags(tags)
|
||||
return item, nil
|
||||
}
|
||||
|
||||
func (v *PostTypeContext[T]) New(item T) (T, error) {
|
||||
item, err := v.MapCategoriesAndTags(item)
|
||||
if err != nil {
|
||||
return item, err
|
||||
}
|
||||
|
||||
var realmId *uint
|
||||
if realm != nil {
|
||||
if !realm.IsPublic {
|
||||
if item.GetRealm() != nil {
|
||||
if !item.GetRealm().IsPublic {
|
||||
var member models.RealmMember
|
||||
if err := database.C.Where(&models.RealmMember{
|
||||
RealmID: realm.ID,
|
||||
AccountID: user.ID,
|
||||
RealmID: item.GetRealm().ID,
|
||||
AccountID: item.GetAuthor().ID,
|
||||
}).First(&member).Error; err != nil {
|
||||
return post, fmt.Errorf("you aren't a part of that realm")
|
||||
return item, fmt.Errorf("you aren't a part of that realm")
|
||||
}
|
||||
}
|
||||
realmId = &realm.ID
|
||||
}
|
||||
|
||||
if publishedAt == nil {
|
||||
publishedAt = lo.ToPtr(time.Now())
|
||||
if err := database.C.Save(&item).Error; err != nil {
|
||||
return item, err
|
||||
}
|
||||
|
||||
post = models.Post{
|
||||
Content: content,
|
||||
Attachments: attachments,
|
||||
Hashtags: tags,
|
||||
Categories: categories,
|
||||
AuthorID: user.ID,
|
||||
RealmID: realmId,
|
||||
PublishedAt: *publishedAt,
|
||||
RepostID: repostTo,
|
||||
ReplyID: replyTo,
|
||||
}
|
||||
|
||||
if err := database.C.Save(&post).Error; err != nil {
|
||||
return post, err
|
||||
}
|
||||
|
||||
if post.ReplyID != nil {
|
||||
var op models.Post
|
||||
if err := database.C.Where(&models.Post{
|
||||
BaseModel: models.BaseModel{ID: *post.ReplyID},
|
||||
}).Preload("Author").First(&op).Error; err == nil {
|
||||
if op.Author.ID != user.ID {
|
||||
postUrl := fmt.Sprintf("https://%s/posts/%d", viper.GetString("domain"), post.ID)
|
||||
if item.GetReplyTo() != nil {
|
||||
go func() {
|
||||
var op models.Moment
|
||||
if err := database.C.Where("id = ?", item.GetReplyTo()).Preload("Author").First(&op).Error; err == nil {
|
||||
if op.Author.ID != item.GetAuthor().ID {
|
||||
postUrl := fmt.Sprintf("https://%s/posts/%d", viper.GetString("domain"), item.GetID())
|
||||
err := NotifyAccount(
|
||||
op.Author,
|
||||
fmt.Sprintf("%s replied you", user.Name),
|
||||
fmt.Sprintf("%s replied your post. Check it out!", user.Name),
|
||||
fmt.Sprintf("%s replied you", item.GetAuthor().Name),
|
||||
fmt.Sprintf("%s replied your post. Check it out!", item.GetAuthor().Name),
|
||||
&proto.NotifyLink{Label: "Related post", Url: postUrl},
|
||||
)
|
||||
if err != nil {
|
||||
@ -235,25 +275,23 @@ func NewPost(
|
||||
}
|
||||
}
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
go func() {
|
||||
var subscribers []models.AccountMembership
|
||||
if err := database.C.Where(&models.AccountMembership{
|
||||
FollowingID: user.ID,
|
||||
}).Preload("Follower").Find(&subscribers).Error; err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
FollowingID: item.GetAuthor().ID,
|
||||
}).Preload("Follower").Find(&subscribers).Error; err == nil && len(subscribers) > 0 {
|
||||
go func() {
|
||||
accounts := lo.Map(subscribers, func(item models.AccountMembership, index int) models.Account {
|
||||
return item.Follower
|
||||
})
|
||||
|
||||
for _, account := range accounts {
|
||||
postUrl := fmt.Sprintf("https://%s/posts/%d", viper.GetString("domain"), post.ID)
|
||||
postUrl := fmt.Sprintf("https://%s/posts/%d", viper.GetString("domain"), item.GetID())
|
||||
err := NotifyAccount(
|
||||
account,
|
||||
fmt.Sprintf("%s just posted a post", user.Name),
|
||||
fmt.Sprintf("%s just posted a post", item.GetAuthor().Name),
|
||||
"Account you followed post a brand new post. Check it out!",
|
||||
&proto.NotifyLink{Label: "Related post", Url: postUrl},
|
||||
)
|
||||
@ -262,85 +300,52 @@ func NewPost(
|
||||
}
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
return post, nil
|
||||
return item, nil
|
||||
}
|
||||
|
||||
func EditPost(
|
||||
post models.Post,
|
||||
content string,
|
||||
publishedAt *time.Time,
|
||||
categories []models.Category,
|
||||
tags []models.Tag,
|
||||
attachments []models.Attachment,
|
||||
) (models.Post, error) {
|
||||
var err error
|
||||
for idx, category := range categories {
|
||||
categories[idx], err = GetCategory(category.Alias)
|
||||
func (v *PostTypeContext[T]) Edit(item T) (T, error) {
|
||||
item, err := v.MapCategoriesAndTags(item)
|
||||
if err != nil {
|
||||
return post, err
|
||||
}
|
||||
}
|
||||
for idx, tag := range tags {
|
||||
tags[idx], err = GetTagOrCreate(tag.Alias, tag.Name)
|
||||
if err != nil {
|
||||
return post, err
|
||||
}
|
||||
return item, err
|
||||
}
|
||||
|
||||
if publishedAt == nil {
|
||||
publishedAt = lo.ToPtr(time.Now())
|
||||
}
|
||||
err = database.C.Save(&item).Error
|
||||
|
||||
post.Content = content
|
||||
post.PublishedAt = *publishedAt
|
||||
post.Hashtags = tags
|
||||
post.Categories = categories
|
||||
post.Attachments = attachments
|
||||
|
||||
err = database.C.Save(&post).Error
|
||||
|
||||
return post, err
|
||||
return item, err
|
||||
}
|
||||
|
||||
func LikePost(user models.Account, post models.Post) (bool, error) {
|
||||
var like models.PostLike
|
||||
if err := database.C.Where(&models.PostLike{
|
||||
AccountID: user.ID,
|
||||
PostID: post.ID,
|
||||
}).First(&like).Error; err != nil {
|
||||
if !errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return true, err
|
||||
}
|
||||
like = models.PostLike{
|
||||
AccountID: user.ID,
|
||||
PostID: post.ID,
|
||||
}
|
||||
return true, database.C.Save(&like).Error
|
||||
func (v *PostTypeContext[T]) Delete(item T) error {
|
||||
return database.C.Delete(&item).Error
|
||||
}
|
||||
|
||||
func (v *PostTypeContext[T]) ReactLike(user models.Account, id uint) (bool, error) {
|
||||
var count int64
|
||||
table := viper.GetString("database.prefix") + v.GetTableName() + "_likes"
|
||||
tx := database.C.Where("account_id = ?", user.ID).Where(v.GetTableName()+"id = ?", id)
|
||||
if tx.Count(&count); count <= 0 {
|
||||
return true, database.C.Table(table).Create(map[string]any{
|
||||
"AccountID": user.ID,
|
||||
v.TypeName + "ID": id,
|
||||
}).Error
|
||||
} else {
|
||||
return false, database.C.Delete(&like).Error
|
||||
column := strings.ToLower(v.TypeName)
|
||||
return false, tx.Raw(fmt.Sprintf("DELETE FROM %s WHERE account_id = ? AND %s_id = ?", table, column), user.ID, id).Error
|
||||
}
|
||||
}
|
||||
|
||||
func DislikePost(user models.Account, post models.Post) (bool, error) {
|
||||
var dislike models.PostDislike
|
||||
if err := database.C.Where(&models.PostDislike{
|
||||
AccountID: user.ID,
|
||||
PostID: post.ID,
|
||||
}).First(&dislike).Error; err != nil {
|
||||
if !errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return true, err
|
||||
}
|
||||
dislike = models.PostDislike{
|
||||
AccountID: user.ID,
|
||||
PostID: post.ID,
|
||||
}
|
||||
return true, database.C.Save(&dislike).Error
|
||||
func (v *PostTypeContext[T]) ReactDislike(user models.Account, id uint) (bool, error) {
|
||||
var count int64
|
||||
table := viper.GetString("database.prefix") + v.GetTableName() + "_dislikes"
|
||||
tx := database.C.Where("account_id = ?", user.ID).Where(v.GetTableName()+"id = ?", id)
|
||||
if tx.Count(&count); count <= 0 {
|
||||
return true, database.C.Table(table).Create(map[string]any{
|
||||
"AccountID": user.ID,
|
||||
v.TypeName + "ID": id,
|
||||
}).Error
|
||||
} else {
|
||||
return false, database.C.Delete(&dislike).Error
|
||||
column := strings.ToLower(v.TypeName)
|
||||
return false, tx.Raw(fmt.Sprintf("DELETE FROM %s WHERE account_id = ? AND %s_id = ?", table, column), user.ID, id).Error
|
||||
}
|
||||
}
|
||||
|
||||
func DeletePost(post models.Post) error {
|
||||
return database.C.Delete(&post).Error
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user