♻️ Interactive v2 #1
							
								
								
									
										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 | ||||
|   | ||||
| @@ -8,19 +8,24 @@ import "time" | ||||
| type Account struct { | ||||
| 	BaseModel | ||||
|  | ||||
| 	Name            string        `json:"name"` | ||||
| 	Nick            string        `json:"nick"` | ||||
| 	Avatar          string        `json:"avatar"` | ||||
| 	Description     string        `json:"description"` | ||||
| 	EmailAddress    string        `json:"email_address"` | ||||
| 	PowerLevel      int           `json:"power_level"` | ||||
| 	Posts           []Post        `json:"posts" gorm:"foreignKey:AuthorID"` | ||||
| 	Attachments     []Attachment  `json:"attachments" gorm:"foreignKey:AuthorID"` | ||||
| 	LikedPosts      []PostLike    `json:"liked_posts"` | ||||
| 	DislikedPosts   []PostDislike `json:"disliked_posts"` | ||||
| 	RealmIdentities []RealmMember `json:"identities"` | ||||
| 	Realms          []Realm       `json:"realms"` | ||||
| 	ExternalID      uint          `json:"external_id"` | ||||
| 	Name             string           `json:"name"` | ||||
| 	Nick             string           `json:"nick"` | ||||
| 	Avatar           string           `json:"avatar"` | ||||
| 	Description      string           `json:"description"` | ||||
| 	EmailAddress     string           `json:"email_address"` | ||||
| 	PowerLevel       int              `json:"power_level"` | ||||
| 	Moments          []Moment         `json:"moments" gorm:"foreignKey:AuthorID"` | ||||
| 	Articles         []Article        `json:"articles" gorm:"foreignKey:AuthorID"` | ||||
| 	Attachments      []Attachment     `json:"attachments" gorm:"foreignKey:AuthorID"` | ||||
| 	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"` | ||||
| } | ||||
|  | ||||
| type AccountMembership struct { | ||||
|   | ||||
							
								
								
									
										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,18 +6,29 @@ import ( | ||||
| 	"path/filepath" | ||||
| ) | ||||
|  | ||||
| type AttachmentType = uint8 | ||||
|  | ||||
| const ( | ||||
| 	AttachmentOthers = AttachmentType(iota) | ||||
| 	AttachmentPhoto | ||||
| 	AttachmentVideo | ||||
| 	AttachmentAudio | ||||
| ) | ||||
|  | ||||
| type Attachment struct { | ||||
| 	BaseModel | ||||
|  | ||||
| 	FileID      string  `json:"file_id"` | ||||
| 	Filesize    int64   `json:"filesize"` | ||||
| 	Filename    string  `json:"filename"` | ||||
| 	Mimetype    string  `json:"mimetype"` | ||||
| 	ExternalUrl string  `json:"external_url"` | ||||
| 	Post        *Post   `json:"post"` | ||||
| 	Author      Account `json:"author"` | ||||
| 	PostID      *uint   `json:"post_id"` | ||||
| 	AuthorID    uint    `json:"author_id"` | ||||
| 	FileID      string         `json:"file_id"` | ||||
| 	Filesize    int64          `json:"filesize"` | ||||
| 	Filename    string         `json:"filename"` | ||||
| 	Mimetype    string         `json:"mimetype"` | ||||
| 	Type        AttachmentType `json:"type"` | ||||
| 	ExternalUrl string         `json:"external_url"` | ||||
| 	Author      Account        `json:"author"` | ||||
| 	ArticleID   *uint          `json:"article_id"` | ||||
| 	MomentID    *uint          `json:"moment_id"` | ||||
| 	CommentID   *uint          `json:"comment_id"` | ||||
| 	AuthorID    uint           `json:"author_id"` | ||||
| } | ||||
|  | ||||
| func (v Attachment) GetStoragePath() string { | ||||
|   | ||||
| @@ -3,17 +3,21 @@ package models | ||||
| type Tag struct { | ||||
| 	BaseModel | ||||
|  | ||||
| 	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"` | ||||
| 	Alias       string    `json:"alias" gorm:"uniqueIndex" validate:"lowercase,alphanum,min=4,max=24"` | ||||
| 	Name        string    `json:"name"` | ||||
| 	Description string    `json:"description"` | ||||
| 	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 { | ||||
| 	BaseModel | ||||
|  | ||||
| 	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"` | ||||
| 	Alias       string    `json:"alias" gorm:"uniqueIndex" validate:"lowercase,alphanum,min=4,max=24"` | ||||
| 	Name        string    `json:"name"` | ||||
| 	Description string    `json:"description"` | ||||
| 	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,26 +1,26 @@ | ||||
| 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"` | ||||
| 	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"` | ||||
| 	AuthorID         uint          `json:"author_id"` | ||||
| 	Author           Account       `json:"author"` | ||||
| 	Alias       string       `json:"alias" gorm:"uniqueIndex"` | ||||
| 	Attachments []Attachment `json:"attachments"` | ||||
| 	PublishedAt *time.Time   `json:"published_at"` | ||||
|  | ||||
| 	AuthorID uint    `json:"author_id"` | ||||
| 	Author   Account `json:"author"` | ||||
|  | ||||
| 	// Dynamic Calculating Values | ||||
| 	LikeCount    int64 `json:"like_count" gorm:"-"` | ||||
| @@ -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 | ||||
|                     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 = ?`, prefix, prefix, prefix, prefix, prefix), post.ID).Scan(&reactInfo) | ||||
|        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` | ||||
| 	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` | ||||
| ) | ||||
|  | ||||
| 	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,229 +197,155 @@ 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 | ||||
| } | ||||
|  | ||||
| 	var realmId *uint | ||||
| 	if realm != nil { | ||||
| 		if !realm.IsPublic { | ||||
| func (v *PostTypeContext[T]) New(item T) (T, error) { | ||||
| 	item, err := v.MapCategoriesAndTags(item) | ||||
| 	if err != nil { | ||||
| 		return item, err | ||||
| 	} | ||||
|  | ||||
| 	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 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", 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 { | ||||
| 						log.Error().Err(err).Msg("An error occurred when notifying user...") | ||||
| 					} | ||||
| 				} | ||||
| 			} | ||||
| 		}() | ||||
| 	} | ||||
|  | ||||
| 	if err := database.C.Save(&post).Error; err != nil { | ||||
| 		return post, err | ||||
| 	} | ||||
| 	var subscribers []models.AccountMembership | ||||
| 	if err := database.C.Where(&models.AccountMembership{ | ||||
| 		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 | ||||
| 			}) | ||||
|  | ||||
| 	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) | ||||
| 			for _, account := range accounts { | ||||
| 				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), | ||||
| 					account, | ||||
| 					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}, | ||||
| 				) | ||||
| 				if err != nil { | ||||
| 					log.Error().Err(err).Msg("An error occurred when notifying user...") | ||||
| 				} | ||||
| 			} | ||||
| 		} | ||||
| 		}() | ||||
| 	} | ||||
|  | ||||
| 	go func() { | ||||
| 		var subscribers []models.AccountMembership | ||||
| 		if err := database.C.Where(&models.AccountMembership{ | ||||
| 			FollowingID: user.ID, | ||||
| 		}).Preload("Follower").Find(&subscribers).Error; err != nil { | ||||
| 			return | ||||
| 		} | ||||
|  | ||||
| 		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) | ||||
| 			err := NotifyAccount( | ||||
| 				account, | ||||
| 				fmt.Sprintf("%s just posted a post", user.Name), | ||||
| 				"Account you followed post a brand new post. Check it out!", | ||||
| 				&proto.NotifyLink{Label: "Related post", Url: postUrl}, | ||||
| 			) | ||||
| 			if err != nil { | ||||
| 				log.Error().Err(err).Msg("An error occurred when notifying user...") | ||||
| 			} | ||||
| 		} | ||||
| 	}() | ||||
|  | ||||
| 	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) | ||||
| 		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 | ||||
| 		} | ||||
| func (v *PostTypeContext[T]) Edit(item T) (T, error) { | ||||
| 	item, err := v.MapCategoriesAndTags(item) | ||||
| 	if err != nil { | ||||
| 		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 | ||||
| } | ||||
|   | ||||
		Reference in New Issue
	
	Block a user