🗑️ Clean up code
This commit is contained in:
parent
da557fbe60
commit
4709760edc
12
.idea/dataSources.xml
generated
Normal file
12
.idea/dataSources.xml
generated
Normal file
@ -0,0 +1,12 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="DataSourceManagerImpl" format="xml" multifile-model="true">
|
||||
<data-source source="LOCAL" name="hy_interactive@localhost" uuid="a2f70c83-03f8-4240-bb8b-ac697502cfe2">
|
||||
<driver-ref>postgresql</driver-ref>
|
||||
<synchronize>true</synchronize>
|
||||
<jdbc-driver>org.postgresql.Driver</jdbc-driver>
|
||||
<jdbc-url>jdbc:postgresql://localhost:5432/hy_interactive</jdbc-url>
|
||||
<working-dir>$ProjectFileDir$</working-dir>
|
||||
</data-source>
|
||||
</component>
|
||||
</project>
|
@ -5,23 +5,19 @@ import (
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
var DatabaseAutoActionRange = []any{
|
||||
var AutoMaintainRange = []any{
|
||||
&models.Account{},
|
||||
&models.Realm{},
|
||||
&models.Category{},
|
||||
&models.Tag{},
|
||||
&models.Moment{},
|
||||
&models.Article{},
|
||||
&models.Comment{},
|
||||
&models.Post{},
|
||||
&models.Reaction{},
|
||||
&models.Attachment{},
|
||||
}
|
||||
|
||||
func RunMigration(source *gorm.DB) error {
|
||||
if err := source.AutoMigrate(
|
||||
append([]any{
|
||||
&models.AccountMembership{},
|
||||
}, DatabaseAutoActionRange...)...,
|
||||
AutoMaintainRange...,
|
||||
); err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -1,7 +1,5 @@
|
||||
package models
|
||||
|
||||
import "time"
|
||||
|
||||
// Account profiles basically fetched from Hydrogen.Passport
|
||||
// But cache at here for better usage
|
||||
// At the same time this model can make relations between local models
|
||||
@ -15,19 +13,8 @@ type Account struct {
|
||||
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"`
|
||||
Posts []Post `json:"posts" gorm:"foreignKey:AuthorID"`
|
||||
Attachments []Attachment `json:"attachments" gorm:"foreignKey:AuthorID"`
|
||||
Reactions []Reaction `json:"reactions"`
|
||||
ExternalID uint `json:"external_id"`
|
||||
}
|
||||
|
||||
type AccountMembership struct {
|
||||
ID uint `json:"id" gorm:"primaryKey"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
UpdatedAt time.Time `json:"updated_at"`
|
||||
Follower Account `json:"follower"`
|
||||
Following Account `json:"following"`
|
||||
FollowerID uint
|
||||
FollowingID uint
|
||||
}
|
||||
|
@ -1,41 +0,0 @@
|
||||
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"`
|
||||
Reactions []Reaction `json:"reactions"`
|
||||
Attachments []Attachment `json:"attachments"`
|
||||
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
|
||||
}
|
@ -19,18 +19,16 @@ const (
|
||||
type Attachment struct {
|
||||
BaseModel
|
||||
|
||||
FileID string `json:"file_id"`
|
||||
Filesize int64 `json:"filesize"`
|
||||
Filename string `json:"filename"`
|
||||
Mimetype string `json:"mimetype"`
|
||||
Hashcode string `json:"hashcode"`
|
||||
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"`
|
||||
FileID string `json:"file_id"`
|
||||
Filesize int64 `json:"filesize"`
|
||||
Filename string `json:"filename"`
|
||||
Mimetype string `json:"mimetype"`
|
||||
Hashcode string `json:"hashcode"`
|
||||
Type AttachmentType `json:"type"`
|
||||
Author Account `json:"author"`
|
||||
AuthorID uint `json:"author_id"`
|
||||
|
||||
PostID *uint `json:"post_id"`
|
||||
}
|
||||
|
||||
func (v Attachment) GetStoragePath() string {
|
||||
|
@ -3,21 +3,17 @@ 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"`
|
||||
Articles []Article `json:"articles" gorm:"many2many:article_tags"`
|
||||
Moments []Moment `json:"moments" gorm:"many2many:moment_tags"`
|
||||
Comments []Comment `json:"comments" gorm:"many2many:comment_tags"`
|
||||
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"`
|
||||
}
|
||||
|
||||
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"`
|
||||
Articles []Article `json:"articles" gorm:"many2many:article_categories"`
|
||||
Moments []Moment `json:"moments" gorm:"many2many:moment_categories"`
|
||||
Comments []Comment `json:"comments" gorm:"many2many:comment_categories"`
|
||||
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_categories"`
|
||||
}
|
||||
|
@ -1,38 +0,0 @@
|
||||
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"`
|
||||
Reactions []Reaction `json:"reactions"`
|
||||
Attachments []Attachment `json:"attachments"`
|
||||
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
|
||||
}
|
@ -1,22 +0,0 @@
|
||||
package models
|
||||
|
||||
type Feed struct {
|
||||
BaseModel
|
||||
|
||||
Alias string `json:"alias"`
|
||||
Title string `json:"title"`
|
||||
Description string `json:"description"`
|
||||
Content string `json:"content"`
|
||||
ModelType string `json:"model_type"`
|
||||
|
||||
CommentCount int64 `json:"comment_count"`
|
||||
ReactionCount int64 `json:"reaction_count"`
|
||||
|
||||
AuthorID uint `json:"author_id"`
|
||||
RealmID *uint `json:"realm_id"`
|
||||
|
||||
Author Account `json:"author" gorm:"embedded"`
|
||||
|
||||
Attachments []Attachment `json:"attachments" gorm:"-"`
|
||||
ReactionList map[string]int64 `json:"reaction_list" gorm:"-"`
|
||||
}
|
@ -1,41 +0,0 @@
|
||||
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"`
|
||||
Reactions []Reaction `json:"reactions"`
|
||||
Attachments []Attachment `json:"attachments"`
|
||||
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
|
||||
}
|
@ -12,53 +12,30 @@ type PostReactInfo struct {
|
||||
RepostCount int64 `json:"repost_count"`
|
||||
}
|
||||
|
||||
type PostBase struct {
|
||||
type Post struct {
|
||||
BaseModel
|
||||
|
||||
Alias string `json:"alias" gorm:"uniqueIndex"`
|
||||
Alias string `json:"alias" gorm:"uniqueIndex"`
|
||||
Content string `json:"content"`
|
||||
Tags []Tag `json:"tags" gorm:"many2many:post_tags"`
|
||||
Categories []Category `json:"categories" gorm:"many2many:post_categories"`
|
||||
Reactions []Reaction `json:"reactions"`
|
||||
Attachments []Attachment `json:"attachments"`
|
||||
Replies []Post `json:"replies" gorm:"foreignKey:ReplyID"`
|
||||
ReplyID *uint `json:"reply_id"`
|
||||
RepostID *uint `json:"repost_id"`
|
||||
RealmID *uint `json:"realm_id"`
|
||||
ReplyTo *Post `json:"reply_to" gorm:"foreignKey:ReplyID"`
|
||||
RepostTo *Post `json:"repost_to" gorm:"foreignKey:RepostID"`
|
||||
Realm *Realm `json:"realm"`
|
||||
|
||||
PublishedAt *time.Time `json:"published_at"`
|
||||
|
||||
AuthorID uint `json:"author_id"`
|
||||
Author Account `json:"author"`
|
||||
|
||||
// Dynamic Calculated Values
|
||||
ReactionList map[string]int64 `json:"reaction_list" 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) SetReactionList(list map[string]int64) {
|
||||
p.ReactionList = list
|
||||
}
|
||||
|
||||
type PostInterface interface {
|
||||
GetID() uint
|
||||
GetHashtags() []Tag
|
||||
GetCategories() []Category
|
||||
GetReplyTo() PostInterface
|
||||
GetRepostTo() PostInterface
|
||||
GetAuthor() Account
|
||||
GetRealm() *Realm
|
||||
|
||||
SetHashtags([]Tag)
|
||||
SetCategories([]Category)
|
||||
SetReactionList(map[string]int64)
|
||||
ReplyCount int64 `json:"comment_count"`
|
||||
ReactionCount int64 `json:"reaction_count"`
|
||||
ReactionList map[string]int64 `json:"reaction_list" gorm:"-"`
|
||||
}
|
||||
|
@ -20,8 +20,6 @@ type Reaction struct {
|
||||
Symbol string `json:"symbol"`
|
||||
Attitude ReactionAttitude `json:"attitude"`
|
||||
|
||||
ArticleID *uint `json:"article_id"`
|
||||
MomentID *uint `json:"moment_id"`
|
||||
CommentID *uint `json:"comment_id"`
|
||||
PostID *uint `json:"post_id"`
|
||||
AccountID uint `json:"account_id"`
|
||||
}
|
||||
|
@ -5,12 +5,11 @@ package models
|
||||
type Realm struct {
|
||||
BaseModel
|
||||
|
||||
Alias string `json:"alias"`
|
||||
Name string `json:"name"`
|
||||
Description string `json:"description"`
|
||||
Articles []Article `json:"article"`
|
||||
Moments []Moment `json:"moments"`
|
||||
IsPublic bool `json:"is_public"`
|
||||
IsCommunity bool `json:"is_community"`
|
||||
ExternalID uint `json:"external_id"`
|
||||
Alias string `json:"alias"`
|
||||
Name string `json:"name"`
|
||||
Description string `json:"description"`
|
||||
Posts []Post `json:"posts"`
|
||||
IsPublic bool `json:"is_public"`
|
||||
IsCommunity bool `json:"is_community"`
|
||||
ExternalID uint `json:"external_id"`
|
||||
}
|
||||
|
@ -1,141 +0,0 @@
|
||||
package server
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"git.solsynth.dev/hydrogen/interactive/pkg/database"
|
||||
"git.solsynth.dev/hydrogen/interactive/pkg/models"
|
||||
"git.solsynth.dev/hydrogen/interactive/pkg/services"
|
||||
"github.com/gofiber/fiber/v2"
|
||||
"github.com/google/uuid"
|
||||
)
|
||||
|
||||
func contextArticle() *services.PostTypeContext {
|
||||
return &services.PostTypeContext{
|
||||
Tx: database.C,
|
||||
TableName: "articles",
|
||||
ColumnName: "article",
|
||||
CanReply: false,
|
||||
CanRepost: false,
|
||||
}
|
||||
}
|
||||
|
||||
func createArticle(c *fiber.Ctx) error {
|
||||
user := c.Locals("principal").(models.Account)
|
||||
|
||||
var data struct {
|
||||
Alias string `json:"alias" form:"alias"`
|
||||
Title string `json:"title" form:"title" validate:"required"`
|
||||
Description string `json:"description" form:"description"`
|
||||
Content string `json:"content" form:"content" validate:"required"`
|
||||
Hashtags []models.Tag `json:"hashtags" form:"hashtags"`
|
||||
Categories []models.Category `json:"categories" form:"categories"`
|
||||
Attachments []models.Attachment `json:"attachments" form:"attachments"`
|
||||
PublishedAt *time.Time `json:"published_at" form:"published_at"`
|
||||
RealmAlias string `json:"realm" form:"realm"`
|
||||
}
|
||||
|
||||
if err := BindAndValidate(c, &data); err != nil {
|
||||
return err
|
||||
} else if len(data.Alias) == 0 {
|
||||
data.Alias = strings.ReplaceAll(uuid.NewString(), "-", "")
|
||||
}
|
||||
|
||||
item := &models.Article{
|
||||
PostBase: models.PostBase{
|
||||
Alias: data.Alias,
|
||||
PublishedAt: data.PublishedAt,
|
||||
AuthorID: user.ID,
|
||||
},
|
||||
Hashtags: data.Hashtags,
|
||||
Categories: data.Categories,
|
||||
Attachments: data.Attachments,
|
||||
Title: data.Title,
|
||||
Description: data.Description,
|
||||
Content: data.Content,
|
||||
}
|
||||
|
||||
if len(data.RealmAlias) > 0 {
|
||||
if realm, err := services.GetRealmWithAlias(data.RealmAlias); err != nil {
|
||||
return fiber.NewError(fiber.StatusBadRequest, err.Error())
|
||||
} else if _, err := services.GetRealmMember(realm.ExternalID, user.ExternalID); err != nil {
|
||||
return fiber.NewError(fiber.StatusBadRequest, fmt.Sprintf("you aren't a part of related realm: %v", err))
|
||||
} else {
|
||||
item.RealmID = &realm.ID
|
||||
}
|
||||
}
|
||||
|
||||
if item, err := services.NewPost(item); err != nil {
|
||||
return fiber.NewError(fiber.StatusBadRequest, err.Error())
|
||||
} else {
|
||||
return c.JSON(item)
|
||||
}
|
||||
}
|
||||
|
||||
func editArticle(c *fiber.Ctx) error {
|
||||
user := c.Locals("principal").(models.Account)
|
||||
id, _ := c.ParamsInt("articleId", 0)
|
||||
|
||||
var data struct {
|
||||
Alias string `json:"alias" form:"alias" validate:"required"`
|
||||
Title string `json:"title" form:"title" validate:"required"`
|
||||
Description string `json:"description" form:"description"`
|
||||
Content string `json:"content" form:"content" validate:"required"`
|
||||
PublishedAt *time.Time `json:"published_at" form:"published_at"`
|
||||
Hashtags []models.Tag `json:"hashtags" form:"hashtags"`
|
||||
Categories []models.Category `json:"categories" form:"categories"`
|
||||
Attachments []models.Attachment `json:"attachments" form:"attachments"`
|
||||
}
|
||||
|
||||
if err := BindAndValidate(c, &data); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var item *models.Article
|
||||
if err := database.C.Where(models.Article{
|
||||
PostBase: models.PostBase{
|
||||
BaseModel: models.BaseModel{ID: uint(id)},
|
||||
AuthorID: user.ID,
|
||||
},
|
||||
}).First(&item).Error; err != nil {
|
||||
return fiber.NewError(fiber.StatusNotFound, err.Error())
|
||||
}
|
||||
|
||||
item.Alias = data.Alias
|
||||
item.Title = data.Title
|
||||
item.Description = data.Description
|
||||
item.Content = data.Content
|
||||
item.PublishedAt = data.PublishedAt
|
||||
item.Hashtags = data.Hashtags
|
||||
item.Categories = data.Categories
|
||||
item.Attachments = data.Attachments
|
||||
|
||||
if item, err := services.EditPost(item); err != nil {
|
||||
return fiber.NewError(fiber.StatusBadRequest, err.Error())
|
||||
} else {
|
||||
return c.JSON(item)
|
||||
}
|
||||
}
|
||||
|
||||
func deleteArticle(c *fiber.Ctx) error {
|
||||
user := c.Locals("principal").(models.Account)
|
||||
id, _ := c.ParamsInt("articleId", 0)
|
||||
|
||||
var item *models.Article
|
||||
if err := database.C.Where(models.Article{
|
||||
PostBase: models.PostBase{
|
||||
BaseModel: models.BaseModel{ID: uint(id)},
|
||||
AuthorID: user.ID,
|
||||
},
|
||||
}).First(&item).Error; err != nil {
|
||||
return fiber.NewError(fiber.StatusNotFound, err.Error())
|
||||
}
|
||||
|
||||
if err := services.DeletePost(item); err != nil {
|
||||
return fiber.NewError(fiber.StatusBadRequest, err.Error())
|
||||
}
|
||||
|
||||
return c.SendStatus(fiber.StatusOK)
|
||||
}
|
@ -1,196 +0,0 @@
|
||||
package server
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/spf13/viper"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"git.solsynth.dev/hydrogen/interactive/pkg/database"
|
||||
"git.solsynth.dev/hydrogen/interactive/pkg/models"
|
||||
"git.solsynth.dev/hydrogen/interactive/pkg/services"
|
||||
"github.com/gofiber/fiber/v2"
|
||||
"github.com/google/uuid"
|
||||
)
|
||||
|
||||
func contextComment() *services.PostTypeContext {
|
||||
return &services.PostTypeContext{
|
||||
Tx: database.C,
|
||||
TableName: "comments",
|
||||
ColumnName: "comment",
|
||||
CanReply: false,
|
||||
CanRepost: true,
|
||||
}
|
||||
}
|
||||
|
||||
func listComment(c *fiber.Ctx) error {
|
||||
take := c.QueryInt("take", 0)
|
||||
offset := c.QueryInt("offset", 0)
|
||||
|
||||
alias := c.Params("postId")
|
||||
|
||||
mx := c.Locals(postContextKey).(*services.PostTypeContext).
|
||||
FilterPublishedAt(time.Now())
|
||||
|
||||
item, err := mx.GetViaAlias(alias)
|
||||
if err != nil {
|
||||
return fiber.NewError(fiber.StatusNotFound, err.Error())
|
||||
}
|
||||
|
||||
data, err := mx.ListComment(item.ID, take, offset)
|
||||
if err != nil {
|
||||
return fiber.NewError(fiber.StatusInternalServerError, err.Error())
|
||||
}
|
||||
|
||||
count, err := mx.CountComment(item.ID)
|
||||
if err != nil {
|
||||
return fiber.NewError(fiber.StatusInternalServerError, err.Error())
|
||||
}
|
||||
|
||||
return c.JSON(fiber.Map{
|
||||
"count": count,
|
||||
"data": data,
|
||||
})
|
||||
}
|
||||
|
||||
func createComment(c *fiber.Ctx) error {
|
||||
user := c.Locals("principal").(models.Account)
|
||||
|
||||
var data struct {
|
||||
Content string `json:"content" form:"content" validate:"required"`
|
||||
PublishedAt *time.Time `json:"published_at" form:"published_at"`
|
||||
Hashtags []models.Tag `json:"hashtags" form:"hashtags"`
|
||||
Categories []models.Category `json:"categories" form:"categories"`
|
||||
Attachments []models.Attachment `json:"attachments" form:"attachments"`
|
||||
ReplyTo uint `json:"reply_to" form:"reply_to"`
|
||||
}
|
||||
|
||||
if err := BindAndValidate(c, &data); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
item := &models.Comment{
|
||||
PostBase: models.PostBase{
|
||||
Alias: strings.ReplaceAll(uuid.NewString(), "-", ""),
|
||||
PublishedAt: data.PublishedAt,
|
||||
AuthorID: user.ID,
|
||||
},
|
||||
Hashtags: data.Hashtags,
|
||||
Categories: data.Categories,
|
||||
Attachments: data.Attachments,
|
||||
Content: data.Content,
|
||||
}
|
||||
|
||||
postType := c.Params("postType")
|
||||
alias := c.Params("postId")
|
||||
|
||||
var err error
|
||||
var res models.Feed
|
||||
|
||||
var columnName string
|
||||
var tableName string
|
||||
|
||||
switch postType {
|
||||
case "moments":
|
||||
columnName = "moment"
|
||||
tableName = viper.GetString("database.table_prefix") + "moments"
|
||||
err = database.C.Model(&models.Moment{}).Where("alias = ?", alias).Select("id").First(&res).Error
|
||||
case "articles":
|
||||
columnName = "article"
|
||||
tableName = viper.GetString("database.table_prefix") + "articles"
|
||||
err = database.C.Model(&models.Article{}).Where("alias = ?", alias).Select("id").First(&res).Error
|
||||
default:
|
||||
return fiber.NewError(fiber.StatusBadRequest, "comment must belongs to a resource")
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return fiber.NewError(fiber.StatusBadRequest, fmt.Sprintf("belongs to resource was not found: %v", err))
|
||||
} else {
|
||||
switch postType {
|
||||
case "moments":
|
||||
item.MomentID = &res.ID
|
||||
case "articles":
|
||||
item.ArticleID = &res.ID
|
||||
}
|
||||
}
|
||||
|
||||
var relatedCount int64
|
||||
if data.ReplyTo > 0 {
|
||||
if err := database.C.Where("id = ?", data.ReplyTo).
|
||||
Model(&models.Comment{}).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 {
|
||||
item.ReplyID = &data.ReplyTo
|
||||
}
|
||||
}
|
||||
|
||||
item, err = services.NewPost(item)
|
||||
if err != nil {
|
||||
return fiber.NewError(fiber.StatusBadRequest, err.Error())
|
||||
}
|
||||
|
||||
// Notify the original poster their post is commented by someone
|
||||
go services.CommentNotify(item, res, columnName, tableName)
|
||||
|
||||
return c.JSON(item)
|
||||
}
|
||||
|
||||
func editComment(c *fiber.Ctx) error {
|
||||
user := c.Locals("principal").(models.Account)
|
||||
id, _ := c.ParamsInt("commentId", 0)
|
||||
|
||||
var data struct {
|
||||
Content string `json:"content" form:"content" validate:"required"`
|
||||
PublishedAt *time.Time `json:"published_at" form:"published_at"`
|
||||
Hashtags []models.Tag `json:"hashtags" form:"hashtags"`
|
||||
Categories []models.Category `json:"categories" form:"categories"`
|
||||
}
|
||||
|
||||
if err := BindAndValidate(c, &data); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var item *models.Comment
|
||||
if err := database.C.Where(models.Comment{
|
||||
PostBase: models.PostBase{
|
||||
BaseModel: models.BaseModel{ID: uint(id)},
|
||||
AuthorID: user.ID,
|
||||
},
|
||||
}).First(&item).Error; err != nil {
|
||||
return fiber.NewError(fiber.StatusNotFound, err.Error())
|
||||
}
|
||||
|
||||
item.Content = data.Content
|
||||
item.PublishedAt = data.PublishedAt
|
||||
item.Hashtags = data.Hashtags
|
||||
item.Categories = data.Categories
|
||||
|
||||
if item, err := services.EditPost(item); err != nil {
|
||||
return fiber.NewError(fiber.StatusBadRequest, err.Error())
|
||||
} else {
|
||||
return c.JSON(item)
|
||||
}
|
||||
}
|
||||
|
||||
func deleteComment(c *fiber.Ctx) error {
|
||||
user := c.Locals("principal").(models.Account)
|
||||
id, _ := c.ParamsInt("commentId", 0)
|
||||
|
||||
var item *models.Comment
|
||||
if err := database.C.Where(models.Comment{
|
||||
PostBase: models.PostBase{
|
||||
BaseModel: models.BaseModel{ID: uint(id)},
|
||||
AuthorID: user.ID,
|
||||
},
|
||||
}).First(&item).Error; err != nil {
|
||||
return fiber.NewError(fiber.StatusNotFound, err.Error())
|
||||
}
|
||||
|
||||
if err := services.DeletePost(item); err != nil {
|
||||
return fiber.NewError(fiber.StatusBadRequest, err.Error())
|
||||
}
|
||||
|
||||
return c.SendStatus(fiber.StatusOK)
|
||||
}
|
@ -1,200 +1,49 @@
|
||||
package server
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"git.solsynth.dev/hydrogen/interactive/pkg/services"
|
||||
"strings"
|
||||
|
||||
"git.solsynth.dev/hydrogen/interactive/pkg/database"
|
||||
"git.solsynth.dev/hydrogen/interactive/pkg/models"
|
||||
"git.solsynth.dev/hydrogen/interactive/pkg/services"
|
||||
"github.com/gofiber/fiber/v2"
|
||||
"github.com/samber/lo"
|
||||
"github.com/spf13/viper"
|
||||
)
|
||||
|
||||
const (
|
||||
queryArticle = "id, created_at, updated_at, alias, title, NULL as content, description, realm_id, author_id, 'article' as model_type"
|
||||
queryMoment = "id, created_at, updated_at, alias, NULL as title, content, NULL as description, realm_id, author_id, 'moment' as model_type"
|
||||
)
|
||||
|
||||
func listFeed(c *fiber.Ctx) error {
|
||||
take := c.QueryInt("take", 0)
|
||||
offset := c.QueryInt("offset", 0)
|
||||
realmAlias := c.Query("realm")
|
||||
realmId := c.QueryInt("realmId", 0)
|
||||
|
||||
if take > 20 {
|
||||
take = 20
|
||||
tx := database.C
|
||||
if realmId > 0 {
|
||||
tx = services.FilterWithRealm(tx, uint(realmId))
|
||||
}
|
||||
|
||||
var whereConditions []string
|
||||
|
||||
if len(realmAlias) > 0 {
|
||||
realm, err := services.GetRealmWithAlias(realmAlias)
|
||||
if err != nil {
|
||||
return fiber.NewError(fiber.StatusBadRequest, fmt.Sprintf("related realm was not found: %v", err))
|
||||
}
|
||||
whereConditions = append(whereConditions, fmt.Sprintf("feed.realm_id = %d", realm.ID))
|
||||
}
|
||||
|
||||
var author models.Account
|
||||
if len(c.Query("authorId")) > 0 {
|
||||
var author models.Account
|
||||
if err := database.C.Where(&models.Account{Name: c.Query("authorId")}).First(&author).Error; err != nil {
|
||||
return fiber.NewError(fiber.StatusNotFound, err.Error())
|
||||
} else {
|
||||
whereConditions = append(whereConditions, fmt.Sprintf("feed.author_id = %d", author.ID))
|
||||
}
|
||||
tx = tx.Where("author_id = ?", author.ID)
|
||||
}
|
||||
|
||||
var whereStatement string
|
||||
if len(whereConditions) > 0 {
|
||||
whereStatement += "WHERE " + strings.Join(whereConditions, " AND ")
|
||||
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 result []*models.Feed
|
||||
|
||||
userTable := viper.GetString("database.prefix") + "accounts"
|
||||
commentTable := viper.GetString("database.prefix") + "comments"
|
||||
reactionTable := viper.GetString("database.prefix") + "reactions"
|
||||
|
||||
database.C.Raw(
|
||||
fmt.Sprintf(`SELECT feed.*, author.*,
|
||||
COALESCE(comment_count, 0) AS comment_count,
|
||||
COALESCE(reaction_count, 0) AS reaction_count
|
||||
FROM (? UNION ALL ?) AS feed
|
||||
INNER JOIN %s AS author ON author_id = author.id
|
||||
LEFT JOIN (SELECT article_id, moment_id, COUNT(*) AS comment_count
|
||||
FROM %s
|
||||
GROUP BY article_id, moment_id) AS comments
|
||||
ON (feed.model_type = 'article' AND feed.id = comments.article_id) OR
|
||||
(feed.model_type = 'moment' AND feed.id = comments.moment_id)
|
||||
LEFT JOIN (SELECT article_id, moment_id, COUNT(*) AS reaction_count
|
||||
FROM %s
|
||||
GROUP BY article_id, moment_id) AS reactions
|
||||
ON (feed.model_type = 'article' AND feed.id = reactions.article_id) OR
|
||||
(feed.model_type = 'moment' AND feed.id = reactions.moment_id)
|
||||
%s ORDER BY feed.created_at desc LIMIT ? OFFSET ?`,
|
||||
userTable,
|
||||
commentTable,
|
||||
reactionTable,
|
||||
whereStatement,
|
||||
),
|
||||
database.C.Select(queryArticle).Model(&models.Article{}),
|
||||
database.C.Select(queryMoment).Model(&models.Moment{}),
|
||||
take,
|
||||
offset,
|
||||
).Scan(&result)
|
||||
|
||||
if !c.QueryBool("noReact", false) {
|
||||
var reactions []struct {
|
||||
PostID uint
|
||||
Symbol string
|
||||
Count int64
|
||||
}
|
||||
|
||||
revertReaction := func(dataset string) error {
|
||||
itemMap := lo.SliceToMap(lo.FilterMap(result, func(item *models.Feed, index int) (*models.Feed, bool) {
|
||||
return item, item.ModelType == dataset
|
||||
}), func(item *models.Feed) (uint, *models.Feed) {
|
||||
return item.ID, item
|
||||
})
|
||||
|
||||
idx := lo.Map(lo.Filter(result, func(item *models.Feed, index int) bool {
|
||||
return item.ModelType == dataset
|
||||
}), func(item *models.Feed, index int) uint {
|
||||
return item.ID
|
||||
})
|
||||
|
||||
if err := database.C.Model(&models.Reaction{}).
|
||||
Select(dataset+"_id as post_id, symbol, COUNT(id) as count").
|
||||
Where(dataset+"_id IN (?)", idx).
|
||||
Group("post_id, symbol").
|
||||
Scan(&reactions).Error; err != nil {
|
||||
return fiber.NewError(fiber.StatusInternalServerError, err.Error())
|
||||
}
|
||||
|
||||
list := map[uint]map[string]int64{}
|
||||
for _, info := range reactions {
|
||||
if _, ok := list[info.PostID]; !ok {
|
||||
list[info.PostID] = make(map[string]int64)
|
||||
}
|
||||
list[info.PostID][info.Symbol] = info.Count
|
||||
}
|
||||
|
||||
for k, v := range list {
|
||||
if post, ok := itemMap[k]; ok {
|
||||
post.ReactionList = v
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
if err := revertReaction("article"); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := revertReaction("moment"); err != nil {
|
||||
return err
|
||||
}
|
||||
count, err := services.CountPost(tx)
|
||||
if err != nil {
|
||||
return fiber.NewError(fiber.StatusInternalServerError, err.Error())
|
||||
}
|
||||
|
||||
if !c.QueryBool("noAttachment", false) {
|
||||
revertAttachment := func(dataset string) error {
|
||||
var attachments []struct {
|
||||
models.Attachment
|
||||
|
||||
PostID uint `json:"post_id"`
|
||||
}
|
||||
|
||||
itemMap := lo.SliceToMap(lo.FilterMap(result, func(item *models.Feed, index int) (*models.Feed, bool) {
|
||||
return item, item.ModelType == dataset
|
||||
}), func(item *models.Feed) (uint, *models.Feed) {
|
||||
return item.ID, item
|
||||
})
|
||||
|
||||
idx := lo.Map(lo.Filter(result, func(item *models.Feed, index int) bool {
|
||||
return item.ModelType == dataset
|
||||
}), func(item *models.Feed, index int) uint {
|
||||
return item.ID
|
||||
})
|
||||
|
||||
if err := database.C.
|
||||
Model(&models.Attachment{}).
|
||||
Select(dataset+"_id as post_id, *").
|
||||
Where(dataset+"_id IN (?)", idx).
|
||||
Scan(&attachments).Error; err != nil {
|
||||
return fiber.NewError(fiber.StatusInternalServerError, err.Error())
|
||||
}
|
||||
|
||||
list := map[uint][]models.Attachment{}
|
||||
for _, info := range attachments {
|
||||
list[info.PostID] = append(list[info.PostID], info.Attachment)
|
||||
}
|
||||
|
||||
for k, v := range list {
|
||||
if post, ok := itemMap[k]; ok {
|
||||
post.Attachments = v
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
if err := revertAttachment("article"); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := revertAttachment("moment"); err != nil {
|
||||
return err
|
||||
}
|
||||
items, err := services.ListPost(tx, take, offset)
|
||||
if err != nil {
|
||||
return fiber.NewError(fiber.StatusBadRequest, err.Error())
|
||||
}
|
||||
|
||||
var count int64
|
||||
database.C.Raw(`SELECT COUNT(*) FROM (? UNION ALL ?) as feed`,
|
||||
database.C.Select(queryArticle).Model(&models.Article{}),
|
||||
database.C.Select(queryMoment).Model(&models.Moment{}),
|
||||
).Scan(&count)
|
||||
|
||||
return c.JSON(fiber.Map{
|
||||
"count": count,
|
||||
"data": result,
|
||||
"data": items,
|
||||
})
|
||||
}
|
||||
|
@ -1,147 +0,0 @@
|
||||
package server
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"git.solsynth.dev/hydrogen/interactive/pkg/database"
|
||||
"git.solsynth.dev/hydrogen/interactive/pkg/models"
|
||||
"git.solsynth.dev/hydrogen/interactive/pkg/services"
|
||||
"github.com/gofiber/fiber/v2"
|
||||
"github.com/google/uuid"
|
||||
)
|
||||
|
||||
func contextMoment() *services.PostTypeContext {
|
||||
return &services.PostTypeContext{
|
||||
Tx: database.C,
|
||||
TableName: "moments",
|
||||
ColumnName: "moment",
|
||||
CanReply: false,
|
||||
CanRepost: true,
|
||||
}
|
||||
}
|
||||
|
||||
func createMoment(c *fiber.Ctx) error {
|
||||
user := c.Locals("principal").(models.Account)
|
||||
|
||||
var data struct {
|
||||
Alias string `json:"alias" form:"alias"`
|
||||
Content string `json:"content" form:"content" validate:"required,max=1024"`
|
||||
Hashtags []models.Tag `json:"hashtags" form:"hashtags"`
|
||||
Categories []models.Category `json:"categories" form:"categories"`
|
||||
Attachments []models.Attachment `json:"attachments" form:"attachments"`
|
||||
PublishedAt *time.Time `json:"published_at" form:"published_at"`
|
||||
RealmAlias string `json:"realm" form:"realm"`
|
||||
RepostTo uint `json:"repost_to" form:"repost_to"`
|
||||
}
|
||||
|
||||
if err := BindAndValidate(c, &data); err != nil {
|
||||
return err
|
||||
} else if len(data.Alias) == 0 {
|
||||
data.Alias = strings.ReplaceAll(uuid.NewString(), "-", "")
|
||||
}
|
||||
|
||||
item := &models.Moment{
|
||||
PostBase: models.PostBase{
|
||||
Alias: data.Alias,
|
||||
PublishedAt: data.PublishedAt,
|
||||
AuthorID: user.ID,
|
||||
},
|
||||
Hashtags: data.Hashtags,
|
||||
Categories: data.Categories,
|
||||
Attachments: data.Attachments,
|
||||
Content: data.Content,
|
||||
}
|
||||
|
||||
var relatedCount int64
|
||||
if data.RepostTo > 0 {
|
||||
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 {
|
||||
item.RepostID = &data.RepostTo
|
||||
}
|
||||
}
|
||||
|
||||
if len(data.RealmAlias) > 0 {
|
||||
if realm, err := services.GetRealmWithAlias(data.RealmAlias); err != nil {
|
||||
return fiber.NewError(fiber.StatusBadRequest, err.Error())
|
||||
} else if _, err := services.GetRealmMember(realm.ExternalID, user.ExternalID); err != nil {
|
||||
return fiber.NewError(fiber.StatusBadRequest, fmt.Sprintf("you aren't a part of related realm: %v", err))
|
||||
} else {
|
||||
item.RealmID = &realm.ID
|
||||
}
|
||||
}
|
||||
|
||||
item, err := services.NewPost(item)
|
||||
if err != nil {
|
||||
return fiber.NewError(fiber.StatusBadRequest, err.Error())
|
||||
}
|
||||
|
||||
return c.JSON(item)
|
||||
}
|
||||
|
||||
func editMoment(c *fiber.Ctx) error {
|
||||
user := c.Locals("principal").(models.Account)
|
||||
id, _ := c.ParamsInt("momentId", 0)
|
||||
|
||||
var data struct {
|
||||
Alias string `json:"alias" form:"alias" validate:"required"`
|
||||
Content string `json:"content" form:"content" validate:"required,max=1024"`
|
||||
PublishedAt *time.Time `json:"published_at" form:"published_at"`
|
||||
Hashtags []models.Tag `json:"hashtags" form:"hashtags"`
|
||||
Categories []models.Category `json:"categories" form:"categories"`
|
||||
Attachments []models.Attachment `json:"attachments" form:"attachments"`
|
||||
}
|
||||
|
||||
if err := BindAndValidate(c, &data); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var item *models.Moment
|
||||
if err := database.C.Where(models.Moment{
|
||||
PostBase: models.PostBase{
|
||||
BaseModel: models.BaseModel{ID: uint(id)},
|
||||
AuthorID: user.ID,
|
||||
},
|
||||
}).First(&item).Error; err != nil {
|
||||
return fiber.NewError(fiber.StatusNotFound, err.Error())
|
||||
}
|
||||
|
||||
item.Alias = data.Alias
|
||||
item.Content = data.Content
|
||||
item.PublishedAt = data.PublishedAt
|
||||
item.Hashtags = data.Hashtags
|
||||
item.Categories = data.Categories
|
||||
item.Attachments = data.Attachments
|
||||
|
||||
if item, err := services.EditPost(item); err != nil {
|
||||
return fiber.NewError(fiber.StatusBadRequest, err.Error())
|
||||
} else {
|
||||
return c.JSON(item)
|
||||
}
|
||||
}
|
||||
|
||||
func deleteMoment(c *fiber.Ctx) error {
|
||||
user := c.Locals("principal").(models.Account)
|
||||
id, _ := c.ParamsInt("momentId", 0)
|
||||
|
||||
var item *models.Moment
|
||||
if err := database.C.Where(models.Moment{
|
||||
PostBase: models.PostBase{
|
||||
BaseModel: models.BaseModel{ID: uint(id)},
|
||||
AuthorID: user.ID,
|
||||
},
|
||||
}).First(&item).Error; err != nil {
|
||||
return fiber.NewError(fiber.StatusNotFound, err.Error())
|
||||
}
|
||||
|
||||
if err := services.DeletePost(item); err != nil {
|
||||
return fiber.NewError(fiber.StatusBadRequest, err.Error())
|
||||
}
|
||||
|
||||
return c.SendStatus(fiber.StatusOK)
|
||||
}
|
@ -2,6 +2,8 @@ package server
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/google/uuid"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"git.solsynth.dev/hydrogen/interactive/pkg/database"
|
||||
@ -11,38 +13,17 @@ import (
|
||||
"github.com/samber/lo"
|
||||
)
|
||||
|
||||
var postContextKey = "ptx"
|
||||
|
||||
func useDynamicContext(c *fiber.Ctx) error {
|
||||
postType := c.Params("postType")
|
||||
switch postType {
|
||||
case "articles":
|
||||
c.Locals(postContextKey, contextArticle())
|
||||
case "moments":
|
||||
c.Locals(postContextKey, contextMoment())
|
||||
case "comments":
|
||||
c.Locals(postContextKey, contextComment())
|
||||
default:
|
||||
return fiber.NewError(fiber.StatusBadRequest, "invalid dataset")
|
||||
}
|
||||
|
||||
return c.Next()
|
||||
}
|
||||
|
||||
func getPost(c *fiber.Ctx) error {
|
||||
alias := c.Params("postId")
|
||||
|
||||
mx := c.Locals(postContextKey).(*services.PostTypeContext).
|
||||
FilterPublishedAt(time.Now())
|
||||
|
||||
item, err := mx.GetViaAlias(alias)
|
||||
item, err := services.GetPostWithAlias(alias)
|
||||
if err != nil {
|
||||
return fiber.NewError(fiber.StatusNotFound, err.Error())
|
||||
}
|
||||
|
||||
item.CommentCount = mx.CountComments(item.ID)
|
||||
item.ReactionCount = mx.CountReactions(item.ID)
|
||||
item.ReactionList, err = mx.ListReactions(item.ID)
|
||||
item.ReplyCount = services.CountPostReply(item.ID)
|
||||
item.ReactionCount = services.CountPostReactions(item.ID)
|
||||
item.ReactionList, err = services.ListPostReactions(item.ID)
|
||||
if err != nil {
|
||||
return fiber.NewError(fiber.StatusInternalServerError, err.Error())
|
||||
}
|
||||
@ -55,36 +36,32 @@ func listPost(c *fiber.Ctx) error {
|
||||
offset := c.QueryInt("offset", 0)
|
||||
realmId := c.QueryInt("realmId", 0)
|
||||
|
||||
mx := c.Locals(postContextKey).(*services.PostTypeContext).
|
||||
FilterPublishedAt(time.Now()).
|
||||
FilterRealm(uint(realmId)).
|
||||
SortCreatedAt("desc")
|
||||
tx := database.C
|
||||
if realmId > 0 {
|
||||
tx = services.FilterWithRealm(tx, uint(realmId))
|
||||
}
|
||||
|
||||
var author models.Account
|
||||
if len(c.Query("authorId")) > 0 {
|
||||
var author models.Account
|
||||
if err := database.C.Where(&models.Account{Name: c.Query("authorId")}).First(&author).Error; err != nil {
|
||||
return fiber.NewError(fiber.StatusNotFound, err.Error())
|
||||
}
|
||||
mx = mx.FilterAuthor(author.ID)
|
||||
tx = tx.Where("author_id = ?", author.ID)
|
||||
}
|
||||
|
||||
if len(c.Query("category")) > 0 {
|
||||
mx = mx.FilterWithCategory(c.Query("category"))
|
||||
tx = services.FilterPostWithCategory(tx, c.Query("category"))
|
||||
}
|
||||
if len(c.Query("tag")) > 0 {
|
||||
mx = mx.FilterWithTag(c.Query("tag"))
|
||||
tx = services.FilterPostWithTag(tx, c.Query("tag"))
|
||||
}
|
||||
|
||||
if !c.QueryBool("reply", true) {
|
||||
mx = mx.FilterReply(true)
|
||||
}
|
||||
|
||||
count, err := mx.Count()
|
||||
count, err := services.CountPost(tx)
|
||||
if err != nil {
|
||||
return fiber.NewError(fiber.StatusInternalServerError, err.Error())
|
||||
}
|
||||
|
||||
items, err := mx.List(take, offset)
|
||||
items, err := services.ListPost(tx, take, offset)
|
||||
if err != nil {
|
||||
return fiber.NewError(fiber.StatusBadRequest, err.Error())
|
||||
}
|
||||
@ -95,6 +72,124 @@ func listPost(c *fiber.Ctx) error {
|
||||
})
|
||||
}
|
||||
|
||||
func createPost(c *fiber.Ctx) error {
|
||||
user := c.Locals("principal").(models.Account)
|
||||
|
||||
var data struct {
|
||||
Alias string `json:"alias" form:"alias"`
|
||||
Content string `json:"content" form:"content" validate:"required,max=4096"`
|
||||
Tags []models.Tag `json:"tags" form:"tags"`
|
||||
Categories []models.Category `json:"categories" form:"categories"`
|
||||
Attachments []models.Attachment `json:"attachments" form:"attachments"`
|
||||
PublishedAt *time.Time `json:"published_at" form:"published_at"`
|
||||
RealmAlias string `json:"realm" form:"realm"`
|
||||
RepostTo uint `json:"repost_to" form:"repost_to"`
|
||||
}
|
||||
|
||||
if err := BindAndValidate(c, &data); err != nil {
|
||||
return err
|
||||
} else if len(data.Alias) == 0 {
|
||||
data.Alias = strings.ReplaceAll(uuid.NewString(), "-", "")
|
||||
}
|
||||
|
||||
item := models.Post{
|
||||
Alias: data.Alias,
|
||||
PublishedAt: data.PublishedAt,
|
||||
AuthorID: user.ID,
|
||||
Tags: data.Tags,
|
||||
Categories: data.Categories,
|
||||
Attachments: data.Attachments,
|
||||
Content: data.Content,
|
||||
}
|
||||
|
||||
var relatedCount int64
|
||||
if data.RepostTo > 0 {
|
||||
if err := database.C.Where("id = ?", data.RepostTo).
|
||||
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 {
|
||||
item.RepostID = &data.RepostTo
|
||||
}
|
||||
}
|
||||
|
||||
if len(data.RealmAlias) > 0 {
|
||||
if realm, err := services.GetRealmWithAlias(data.RealmAlias); err != nil {
|
||||
return fiber.NewError(fiber.StatusBadRequest, err.Error())
|
||||
} else if _, err := services.GetRealmMember(realm.ExternalID, user.ExternalID); err != nil {
|
||||
return fiber.NewError(fiber.StatusBadRequest, fmt.Sprintf("you aren't a part of related realm: %v", err))
|
||||
} else {
|
||||
item.RealmID = &realm.ID
|
||||
}
|
||||
}
|
||||
|
||||
item, err := services.NewPost(user, item)
|
||||
if err != nil {
|
||||
return fiber.NewError(fiber.StatusBadRequest, err.Error())
|
||||
}
|
||||
|
||||
return c.JSON(item)
|
||||
}
|
||||
|
||||
func editPost(c *fiber.Ctx) error {
|
||||
user := c.Locals("principal").(models.Account)
|
||||
id, _ := c.ParamsInt("postId", 0)
|
||||
|
||||
var data struct {
|
||||
Alias string `json:"alias" form:"alias" validate:"required"`
|
||||
Content string `json:"content" form:"content" validate:"required,max=1024"`
|
||||
PublishedAt *time.Time `json:"published_at" form:"published_at"`
|
||||
Tags []models.Tag `json:"tags" form:"tags"`
|
||||
Categories []models.Category `json:"categories" form:"categories"`
|
||||
Attachments []models.Attachment `json:"attachments" form:"attachments"`
|
||||
}
|
||||
|
||||
if err := BindAndValidate(c, &data); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var item models.Post
|
||||
if err := database.C.Where(models.Post{
|
||||
BaseModel: models.BaseModel{ID: uint(id)},
|
||||
AuthorID: user.ID,
|
||||
}).First(&item).Error; err != nil {
|
||||
return fiber.NewError(fiber.StatusNotFound, err.Error())
|
||||
}
|
||||
|
||||
item.Alias = data.Alias
|
||||
item.Content = data.Content
|
||||
item.PublishedAt = data.PublishedAt
|
||||
item.Tags = data.Tags
|
||||
item.Categories = data.Categories
|
||||
item.Attachments = data.Attachments
|
||||
|
||||
if item, err := services.EditPost(item); err != nil {
|
||||
return fiber.NewError(fiber.StatusBadRequest, err.Error())
|
||||
} else {
|
||||
return c.JSON(item)
|
||||
}
|
||||
}
|
||||
|
||||
func deletePost(c *fiber.Ctx) error {
|
||||
user := c.Locals("principal").(models.Account)
|
||||
id, _ := c.ParamsInt("postId", 0)
|
||||
|
||||
var item models.Post
|
||||
if err := database.C.Where(models.Post{
|
||||
BaseModel: models.BaseModel{ID: uint(id)},
|
||||
AuthorID: user.ID,
|
||||
}).First(&item).Error; err != nil {
|
||||
return fiber.NewError(fiber.StatusNotFound, err.Error())
|
||||
}
|
||||
|
||||
if err := services.DeletePost(item); err != nil {
|
||||
return fiber.NewError(fiber.StatusBadRequest, err.Error())
|
||||
}
|
||||
|
||||
return c.SendStatus(fiber.StatusOK)
|
||||
}
|
||||
|
||||
func reactPost(c *fiber.Ctx) error {
|
||||
user := c.Locals("principal").(models.Account)
|
||||
|
||||
@ -107,45 +202,23 @@ func reactPost(c *fiber.Ctx) error {
|
||||
return err
|
||||
}
|
||||
|
||||
mx := c.Locals(postContextKey).(*services.PostTypeContext)
|
||||
|
||||
reaction := models.Reaction{
|
||||
Symbol: data.Symbol,
|
||||
Attitude: data.Attitude,
|
||||
AccountID: user.ID,
|
||||
}
|
||||
|
||||
postType := c.Params("postType")
|
||||
alias := c.Params("postId")
|
||||
|
||||
var err error
|
||||
var res models.Feed
|
||||
var res models.Post
|
||||
|
||||
switch postType {
|
||||
case "moments":
|
||||
err = database.C.Model(&models.Moment{}).Where("id = ?", alias).Select("id").First(&res).Error
|
||||
case "articles":
|
||||
err = database.C.Model(&models.Article{}).Where("id = ?", alias).Select("id").First(&res).Error
|
||||
case "comments":
|
||||
err = database.C.Model(&models.Comment{}).Where("id = ?", alias).Select("id").First(&res).Error
|
||||
default:
|
||||
return fiber.NewError(fiber.StatusBadRequest, "comment must belongs to a resource")
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return fiber.NewError(fiber.StatusBadRequest, fmt.Sprintf("belongs to resource was not found: %v", err))
|
||||
if err := database.C.Where("id = ?", alias).Select("id").First(&res).Error; err != nil {
|
||||
return fiber.NewError(fiber.StatusBadRequest, fmt.Sprintf("unable to find post to react: %v", err))
|
||||
} else {
|
||||
switch postType {
|
||||
case "moments":
|
||||
reaction.MomentID = &res.ID
|
||||
case "articles":
|
||||
reaction.ArticleID = &res.ID
|
||||
case "comments":
|
||||
reaction.CommentID = &res.ID
|
||||
}
|
||||
reaction.PostID = &res.ID
|
||||
}
|
||||
|
||||
if positive, reaction, err := mx.React(reaction); err != nil {
|
||||
if positive, reaction, err := services.ReactPost(reaction); err != nil {
|
||||
return fiber.NewError(fiber.StatusBadRequest, err.Error())
|
||||
} else {
|
||||
return c.Status(lo.Ternary(positive, fiber.StatusCreated, fiber.StatusNoContent)).JSON(reaction)
|
||||
|
52
pkg/server/replies_api.go
Normal file
52
pkg/server/replies_api.go
Normal file
@ -0,0 +1,52 @@
|
||||
package server
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"git.solsynth.dev/hydrogen/interactive/pkg/database"
|
||||
"git.solsynth.dev/hydrogen/interactive/pkg/models"
|
||||
"git.solsynth.dev/hydrogen/interactive/pkg/services"
|
||||
"github.com/gofiber/fiber/v2"
|
||||
)
|
||||
|
||||
func listReplies(c *fiber.Ctx) error {
|
||||
take := c.QueryInt("take", 0)
|
||||
offset := c.QueryInt("offset", 0)
|
||||
|
||||
tx := database.C
|
||||
var post models.Post
|
||||
if err := database.C.Where("alias = ?", c.Params("postId")).First(&post).Error; err != nil {
|
||||
return fiber.NewError(fiber.StatusBadRequest, fmt.Sprintf("unable to find post: %v", err))
|
||||
} else {
|
||||
tx = services.FilterPostReply(tx, post.ID)
|
||||
}
|
||||
|
||||
if len(c.Query("authorId")) > 0 {
|
||||
var author models.Account
|
||||
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("author_id = ?", author.ID)
|
||||
}
|
||||
|
||||
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"))
|
||||
}
|
||||
|
||||
count, err := services.CountPost(tx)
|
||||
if err != nil {
|
||||
return fiber.NewError(fiber.StatusInternalServerError, err.Error())
|
||||
}
|
||||
|
||||
items, err := services.ListPost(tx, take, offset)
|
||||
if err != nil {
|
||||
return fiber.NewError(fiber.StatusBadRequest, err.Error())
|
||||
}
|
||||
|
||||
return c.JSON(fiber.Map{
|
||||
"count": count,
|
||||
"data": items,
|
||||
})
|
||||
}
|
@ -62,8 +62,6 @@ func NewServer() {
|
||||
{
|
||||
api.Get("/users/me", authMiddleware, getUserinfo)
|
||||
api.Get("/users/:accountId", getOthersInfo)
|
||||
api.Get("/users/:accountId/follow", authMiddleware, getAccountFollowed)
|
||||
api.Post("/users/:accountId/follow", authMiddleware, doFollowAccount)
|
||||
|
||||
api.Get("/attachments/o/:fileId", readAttachment)
|
||||
api.Post("/attachments", authMiddleware, uploadAttachment)
|
||||
@ -71,33 +69,16 @@ func NewServer() {
|
||||
|
||||
api.Get("/feed", listFeed)
|
||||
|
||||
posts := api.Group("/p/:postType").Use(useDynamicContext).Name("Dataset Universal API")
|
||||
posts := api.Group("/posts").Name("Posts API")
|
||||
{
|
||||
posts.Get("/", listPost)
|
||||
posts.Get("/:postId", getPost)
|
||||
posts.Post("/", authMiddleware, createPost)
|
||||
posts.Post("/:postId/react", authMiddleware, reactPost)
|
||||
posts.Get("/:postId/comments", listComment)
|
||||
posts.Post("/:postId/comments", authMiddleware, createComment)
|
||||
}
|
||||
posts.Put("/:postId", authMiddleware, editPost)
|
||||
posts.Delete("/:postId", authMiddleware, deletePost)
|
||||
|
||||
moments := api.Group("/p/moments").Name("Moments API")
|
||||
{
|
||||
moments.Post("/", authMiddleware, createMoment)
|
||||
moments.Put("/:momentId", authMiddleware, editMoment)
|
||||
moments.Delete("/:momentId", authMiddleware, deleteMoment)
|
||||
}
|
||||
|
||||
articles := api.Group("/p/articles").Name("Articles API")
|
||||
{
|
||||
articles.Post("/", authMiddleware, createArticle)
|
||||
articles.Put("/:articleId", authMiddleware, editArticle)
|
||||
articles.Delete("/:articleId", authMiddleware, deleteArticle)
|
||||
}
|
||||
|
||||
comments := api.Group("/p/comments").Name("Comments API")
|
||||
{
|
||||
comments.Put("/:commentId", authMiddleware, editComment)
|
||||
comments.Delete("/:commentId", authMiddleware, deleteComment)
|
||||
posts.Get("/:postId/replies", listReplies)
|
||||
}
|
||||
|
||||
api.Get("/categories", listCategories)
|
||||
|
@ -3,7 +3,6 @@ package server
|
||||
import (
|
||||
"git.solsynth.dev/hydrogen/interactive/pkg/database"
|
||||
"git.solsynth.dev/hydrogen/interactive/pkg/models"
|
||||
"git.solsynth.dev/hydrogen/interactive/pkg/services"
|
||||
"github.com/gofiber/fiber/v2"
|
||||
)
|
||||
|
||||
@ -32,45 +31,3 @@ func getOthersInfo(c *fiber.Ctx) error {
|
||||
|
||||
return c.JSON(data)
|
||||
}
|
||||
|
||||
func getAccountFollowed(c *fiber.Ctx) error {
|
||||
user := c.Locals("principal").(models.Account)
|
||||
accountId, _ := c.ParamsInt("accountId", 0)
|
||||
|
||||
var data models.Account
|
||||
if err := database.C.
|
||||
Where(&models.Account{BaseModel: models.BaseModel{ID: uint(accountId)}}).
|
||||
First(&data).Error; err != nil {
|
||||
return fiber.NewError(fiber.StatusInternalServerError, err.Error())
|
||||
}
|
||||
|
||||
_, status := services.GetAccountFollowed(user, data)
|
||||
|
||||
return c.JSON(fiber.Map{
|
||||
"is_followed": status,
|
||||
})
|
||||
}
|
||||
|
||||
func doFollowAccount(c *fiber.Ctx) error {
|
||||
user := c.Locals("principal").(models.Account)
|
||||
id, _ := c.ParamsInt("accountId", 0)
|
||||
|
||||
var account models.Account
|
||||
if err := database.C.Where(&models.Account{
|
||||
BaseModel: models.BaseModel{ID: uint(id)},
|
||||
}).First(&account).Error; err != nil {
|
||||
return fiber.NewError(fiber.StatusNotFound, err.Error())
|
||||
}
|
||||
|
||||
if _, ok := services.GetAccountFollowed(user, account); ok {
|
||||
if err := services.UnfollowAccount(user.ID, account.ID); err != nil {
|
||||
return fiber.NewError(fiber.StatusBadRequest, err.Error())
|
||||
}
|
||||
return c.SendStatus(fiber.StatusNoContent)
|
||||
} else {
|
||||
if err := services.FollowAccount(user.ID, account.ID); err != nil {
|
||||
return fiber.NewError(fiber.StatusBadRequest, err.Error())
|
||||
}
|
||||
return c.SendStatus(fiber.StatusCreated)
|
||||
}
|
||||
}
|
||||
|
@ -11,30 +11,6 @@ import (
|
||||
"time"
|
||||
)
|
||||
|
||||
func FollowAccount(followerId, followingId uint) error {
|
||||
relationship := models.AccountMembership{
|
||||
FollowerID: followerId,
|
||||
FollowingID: followingId,
|
||||
}
|
||||
return database.C.Create(&relationship).Error
|
||||
}
|
||||
|
||||
func UnfollowAccount(followerId, followingId uint) error {
|
||||
return database.C.Where(models.AccountMembership{
|
||||
FollowerID: followerId,
|
||||
FollowingID: followingId,
|
||||
}).Delete(&models.AccountMembership{}).Error
|
||||
}
|
||||
|
||||
func GetAccountFollowed(user models.Account, target models.Account) (models.AccountMembership, bool) {
|
||||
var relationship models.AccountMembership
|
||||
err := database.C.Model(&models.AccountMembership{}).
|
||||
Where(&models.AccountMembership{FollowerID: user.ID, FollowingID: target.ID}).
|
||||
First(&relationship).
|
||||
Error
|
||||
return relationship, err == nil
|
||||
}
|
||||
|
||||
func GetAccountFriend(userId, relatedId uint, status int) (*proto.FriendshipResponse, error) {
|
||||
var user models.Account
|
||||
if err := database.C.Where("id = ?", userId).First(&user).Error; err != nil {
|
||||
|
@ -11,7 +11,7 @@ func DoAutoDatabaseCleanup() {
|
||||
log.Debug().Time("deadline", deadline).Msg("Now cleaning up entire database...")
|
||||
|
||||
var count int64
|
||||
for _, model := range database.DatabaseAutoActionRange {
|
||||
for _, model := range database.AutoMaintainRange {
|
||||
tx := database.C.Unscoped().Delete(model, "deleted_at >= ?", deadline)
|
||||
if tx.Error != nil {
|
||||
log.Error().Err(tx.Error).Msg("An error occurred when running auth context cleanup...")
|
||||
|
@ -1,107 +0,0 @@
|
||||
package services
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"git.solsynth.dev/hydrogen/passport/pkg/grpc/proto"
|
||||
"github.com/rs/zerolog/log"
|
||||
"time"
|
||||
|
||||
"git.solsynth.dev/hydrogen/interactive/pkg/database"
|
||||
"git.solsynth.dev/hydrogen/interactive/pkg/models"
|
||||
"github.com/samber/lo"
|
||||
"github.com/spf13/viper"
|
||||
)
|
||||
|
||||
func (v *PostTypeContext) ListComment(id uint, take int, offset int, noReact ...bool) ([]*models.Feed, error) {
|
||||
if take > 20 {
|
||||
take = 20
|
||||
}
|
||||
|
||||
var items []*models.Feed
|
||||
table := viper.GetString("database.prefix") + "comments"
|
||||
userTable := viper.GetString("database.prefix") + "accounts"
|
||||
if err := v.Tx.
|
||||
Table(table).
|
||||
Select("*, ? as model_type", "comment").
|
||||
Where(v.ColumnName+"_id = ?", id).
|
||||
Joins(fmt.Sprintf("INNER JOIN %s as author ON author_id = author.id", userTable)).
|
||||
Limit(take).Offset(offset).Find(&items).Error; err != nil {
|
||||
return items, err
|
||||
}
|
||||
|
||||
idx := lo.Map(items, func(item *models.Feed, index int) uint {
|
||||
return item.ID
|
||||
})
|
||||
|
||||
if len(noReact) <= 0 || !noReact[0] {
|
||||
var reactions []struct {
|
||||
PostID uint
|
||||
Symbol string
|
||||
Count int64
|
||||
}
|
||||
|
||||
if err := database.C.Model(&models.Reaction{}).
|
||||
Select("comment_id as post_id, symbol, COUNT(id) as count").
|
||||
Where("comment_id IN (?)", idx).
|
||||
Group("post_id, symbol").
|
||||
Scan(&reactions).Error; err != nil {
|
||||
return items, err
|
||||
}
|
||||
|
||||
itemMap := lo.SliceToMap(items, func(item *models.Feed) (uint, *models.Feed) {
|
||||
return item.ID, item
|
||||
})
|
||||
|
||||
list := map[uint]map[string]int64{}
|
||||
for _, info := range reactions {
|
||||
if _, ok := list[info.PostID]; !ok {
|
||||
list[info.PostID] = make(map[string]int64)
|
||||
}
|
||||
list[info.PostID][info.Symbol] = info.Count
|
||||
}
|
||||
|
||||
for k, v := range list {
|
||||
if post, ok := itemMap[k]; ok {
|
||||
post.ReactionList = v
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return items, nil
|
||||
}
|
||||
|
||||
func (v *PostTypeContext) CountComment(id uint) (int64, error) {
|
||||
var count int64
|
||||
if err := database.C.
|
||||
Model(&models.Comment{}).
|
||||
Where(v.ColumnName+"_id = ?", id).
|
||||
Where("published_at <= ?", time.Now()).
|
||||
Count(&count).Error; err != nil {
|
||||
return count, err
|
||||
}
|
||||
|
||||
return count, nil
|
||||
}
|
||||
|
||||
func CommentNotify(this models.PostInterface, original models.Feed, columnName, tableName string) {
|
||||
var op models.Feed
|
||||
if err := database.C.
|
||||
Where(columnName+"_id = ?", original.ID).
|
||||
Preload("Author").
|
||||
Table(tableName).
|
||||
First(&op).Error; err == nil {
|
||||
if op.Author.ID != this.GetAuthor().ID {
|
||||
postUrl := fmt.Sprintf("https://%s/posts/%d", viper.GetString("domain"), this.GetID())
|
||||
err := NotifyAccount(
|
||||
op.Author,
|
||||
fmt.Sprintf("%s commented you", this.GetAuthor().Name),
|
||||
fmt.Sprintf("%s commented your post. Check it out!", this.GetAuthor().Name),
|
||||
false,
|
||||
&proto.NotifyLink{Label: "Related post", Url: postUrl},
|
||||
)
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msg("An error occurred when notifying user...")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -1 +0,0 @@
|
||||
package services
|
@ -3,8 +3,6 @@ package services
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"git.solsynth.dev/hydrogen/interactive/pkg/database"
|
||||
"git.solsynth.dev/hydrogen/interactive/pkg/models"
|
||||
"git.solsynth.dev/hydrogen/passport/pkg/grpc/proto"
|
||||
@ -12,131 +10,90 @@ import (
|
||||
"github.com/samber/lo"
|
||||
"github.com/spf13/viper"
|
||||
"gorm.io/gorm"
|
||||
"time"
|
||||
)
|
||||
|
||||
type PostTypeContext struct {
|
||||
Tx *gorm.DB
|
||||
|
||||
TableName string
|
||||
ColumnName string
|
||||
CanReply bool
|
||||
CanRepost bool
|
||||
func FilterPostWithCategory(tx *gorm.DB, alias string) *gorm.DB {
|
||||
return tx.Joins("JOIN post_categories ON posts.id = post_categories.post_id").
|
||||
Joins("JOIN post_categories ON post_categories.id = post_categories.category_id").
|
||||
Where("post_categories.alias = ?", alias)
|
||||
}
|
||||
|
||||
func (v *PostTypeContext) FilterWithCategory(alias string) *PostTypeContext {
|
||||
name := v.ColumnName
|
||||
v.Tx.Joins(fmt.Sprintf("JOIN %s_categories ON %s.id = %s_categories.%s_id", name, v.TableName, name, name)).
|
||||
Joins(fmt.Sprintf("JOIN %s_categories ON %s_categories.id = %s_categories.category_id", name, name, name)).
|
||||
Where(name+"_categories.alias = ?", alias)
|
||||
return v
|
||||
func FilterPostWithTag(tx *gorm.DB, alias string) *gorm.DB {
|
||||
return tx.Joins("JOIN post_tags ON posts.id = post_tags.post_id").
|
||||
Joins("JOIN post_tags ON post_tags.id = post_tags.category_id").
|
||||
Where("post_tags.alias = ?", alias)
|
||||
}
|
||||
|
||||
func (v *PostTypeContext) FilterWithTag(alias string) *PostTypeContext {
|
||||
name := v.ColumnName
|
||||
v.Tx.Joins(fmt.Sprintf("JOIN %s_tags ON %s.id = %s_tags.%s_id", name, v.TableName, name, name)).
|
||||
Joins(fmt.Sprintf("JOIN %s_tags ON %s_tags.id = %s_tags.category_id", name, name, name)).
|
||||
Where(name+"_tags.alias = ?", alias)
|
||||
return v
|
||||
}
|
||||
|
||||
func (v *PostTypeContext) FilterPublishedAt(date time.Time) *PostTypeContext {
|
||||
v.Tx.Where("published_at <= ? AND published_at IS NULL", date)
|
||||
return v
|
||||
}
|
||||
|
||||
func (v *PostTypeContext) FilterRealm(id uint) *PostTypeContext {
|
||||
func FilterWithRealm(tx *gorm.DB, id uint) *gorm.DB {
|
||||
if id > 0 {
|
||||
v.Tx = v.Tx.Where("realm_id = ?", id)
|
||||
return tx.Where("realm_id = ?", id)
|
||||
} else {
|
||||
v.Tx = v.Tx.Where("realm_id IS NULL")
|
||||
return tx.Where("realm_id IS NULL")
|
||||
}
|
||||
return v
|
||||
}
|
||||
|
||||
func (v *PostTypeContext) FilterAuthor(id uint) *PostTypeContext {
|
||||
v.Tx = v.Tx.Where("author_id = ?", id)
|
||||
return v
|
||||
}
|
||||
|
||||
func (v *PostTypeContext) FilterReply(condition bool) *PostTypeContext {
|
||||
if condition {
|
||||
v.Tx = v.Tx.Where("reply_id IS NOT NULL")
|
||||
func FilterPostReply(tx *gorm.DB, replyTo ...uint) *gorm.DB {
|
||||
if len(replyTo) > 0 && replyTo[0] > 0 {
|
||||
return tx.Where("reply_id = ?", replyTo[0])
|
||||
} else {
|
||||
v.Tx = v.Tx.Where("reply_id IS NULL")
|
||||
return tx.Where("reply_id IS NULL")
|
||||
}
|
||||
return v
|
||||
}
|
||||
|
||||
func (v *PostTypeContext) SortCreatedAt(order string) *PostTypeContext {
|
||||
v.Tx.Order(fmt.Sprintf("created_at %s", order))
|
||||
return v
|
||||
func FilterPostWithPublishedAt(tx *gorm.DB, date time.Time) *gorm.DB {
|
||||
return tx.Where("published_at <= ? AND published_at IS NULL", date)
|
||||
}
|
||||
|
||||
func (v *PostTypeContext) GetViaAlias(alias string) (models.Feed, error) {
|
||||
var item models.Feed
|
||||
table := viper.GetString("database.prefix") + v.TableName
|
||||
userTable := viper.GetString("database.prefix") + "accounts"
|
||||
if err := v.Tx.
|
||||
Table(table).
|
||||
Select("*, ? as model_type", v.ColumnName).
|
||||
Joins(fmt.Sprintf("INNER JOIN %s AS author ON author_id = author.id", userTable)).
|
||||
func GetPostWithAlias(alias string, ignoreLimitation ...bool) (models.Post, error) {
|
||||
tx := database.C
|
||||
if len(ignoreLimitation) == 0 || !ignoreLimitation[0] {
|
||||
tx = FilterPostWithPublishedAt(tx, time.Now())
|
||||
}
|
||||
|
||||
var item models.Post
|
||||
if err := tx.
|
||||
Where("alias = ?", alias).
|
||||
Preload("Author").
|
||||
Preload("Attachments").
|
||||
First(&item).Error; err != nil {
|
||||
return item, err
|
||||
}
|
||||
|
||||
var attachments []models.Attachment
|
||||
if err := database.C.
|
||||
Model(&models.Attachment{}).
|
||||
Where(v.ColumnName+"_id = ?", item.ID).
|
||||
Scan(&attachments).Error; err != nil {
|
||||
return item, nil
|
||||
}
|
||||
|
||||
func GetPost(id uint, ignoreLimitation ...bool) (models.Post, error) {
|
||||
tx := database.C
|
||||
if len(ignoreLimitation) == 0 || !ignoreLimitation[0] {
|
||||
tx = FilterPostWithPublishedAt(tx, time.Now())
|
||||
}
|
||||
|
||||
var item models.Post
|
||||
if err := tx.
|
||||
Where("id = ?", id).
|
||||
Preload("Author").
|
||||
Preload("Attachments").
|
||||
First(&item).Error; err != nil {
|
||||
return item, err
|
||||
} else {
|
||||
item.Attachments = attachments
|
||||
}
|
||||
|
||||
return item, nil
|
||||
}
|
||||
|
||||
func (v *PostTypeContext) Get(id uint, noComments ...bool) (models.Feed, error) {
|
||||
var item models.Feed
|
||||
table := viper.GetString("database.prefix") + v.TableName
|
||||
userTable := viper.GetString("database.prefix") + "accounts"
|
||||
if err := v.Tx.
|
||||
Table(table).
|
||||
Select("*, ? as model_type", v.ColumnName).
|
||||
Joins(fmt.Sprintf("INNER JOIN %s AS author ON author_id = author.id", userTable)).
|
||||
Where("id = ?", id).First(&item).Error; err != nil {
|
||||
return item, err
|
||||
}
|
||||
|
||||
var attachments []models.Attachment
|
||||
if err := database.C.
|
||||
Model(&models.Attachment{}).
|
||||
Where(v.ColumnName+"_id = ?", id).
|
||||
Scan(&attachments).Error; err != nil {
|
||||
return item, err
|
||||
} else {
|
||||
item.Attachments = attachments
|
||||
}
|
||||
|
||||
return item, nil
|
||||
}
|
||||
|
||||
func (v *PostTypeContext) Count() (int64, error) {
|
||||
func CountPost(tx *gorm.DB) (int64, error) {
|
||||
var count int64
|
||||
table := viper.GetString("database.prefix") + v.TableName
|
||||
if err := v.Tx.Table(table).Count(&count).Error; err != nil {
|
||||
if err := tx.Model(&models.Post{}).Count(&count).Error; err != nil {
|
||||
return count, err
|
||||
}
|
||||
|
||||
return count, nil
|
||||
}
|
||||
|
||||
func (v *PostTypeContext) CountComments(id uint) int64 {
|
||||
func CountPostReply(id uint) int64 {
|
||||
var count int64
|
||||
if err := database.C.Model(&models.Comment{}).
|
||||
Where(v.ColumnName+"_id = ?", id).
|
||||
if err := database.C.Model(&models.Post{}).
|
||||
Where("reply_id = ?", id).
|
||||
Count(&count).Error; err != nil {
|
||||
return 0
|
||||
}
|
||||
@ -144,10 +101,10 @@ func (v *PostTypeContext) CountComments(id uint) int64 {
|
||||
return count
|
||||
}
|
||||
|
||||
func (v *PostTypeContext) CountReactions(id uint) int64 {
|
||||
func CountPostReactions(id uint) int64 {
|
||||
var count int64
|
||||
if err := database.C.Model(&models.Reaction{}).
|
||||
Where(v.ColumnName+"_id = ?", id).
|
||||
Where("post_id = ?", id).
|
||||
Count(&count).Error; err != nil {
|
||||
return 0
|
||||
}
|
||||
@ -155,7 +112,7 @@ func (v *PostTypeContext) CountReactions(id uint) int64 {
|
||||
return count
|
||||
}
|
||||
|
||||
func (v *PostTypeContext) ListReactions(id uint) (map[string]int64, error) {
|
||||
func ListPostReactions(id uint) (map[string]int64, error) {
|
||||
var reactions []struct {
|
||||
Symbol string
|
||||
Count int64
|
||||
@ -163,7 +120,7 @@ func (v *PostTypeContext) ListReactions(id uint) (map[string]int64, error) {
|
||||
|
||||
if err := database.C.Model(&models.Reaction{}).
|
||||
Select("symbol, COUNT(id) as count").
|
||||
Where(v.ColumnName+"_id = ?", id).
|
||||
Where("post_id = ?", id).
|
||||
Group("symbol").
|
||||
Scan(&reactions).Error; err != nil {
|
||||
return map[string]int64{}, err
|
||||
@ -178,21 +135,22 @@ func (v *PostTypeContext) ListReactions(id uint) (map[string]int64, error) {
|
||||
}), nil
|
||||
}
|
||||
|
||||
func (v *PostTypeContext) List(take int, offset int, noReact ...bool) ([]*models.Feed, error) {
|
||||
func ListPost(tx *gorm.DB, take int, offset int, noReact ...bool) ([]models.Post, error) {
|
||||
if take > 20 {
|
||||
take = 20
|
||||
}
|
||||
|
||||
var items []*models.Feed
|
||||
table := viper.GetString("database.prefix") + v.TableName
|
||||
if err := v.Tx.
|
||||
Table(table).
|
||||
Select("*, ? as model_type", v.ColumnName).
|
||||
Limit(take).Offset(offset).Find(&items).Error; err != nil {
|
||||
var items []models.Post
|
||||
if err := tx.
|
||||
Limit(take).Offset(offset).
|
||||
Order("created_at DESC").
|
||||
Preload("Author").
|
||||
Preload("Attachments").
|
||||
Find(&items).Error; err != nil {
|
||||
return items, err
|
||||
}
|
||||
|
||||
idx := lo.Map(items, func(item *models.Feed, index int) uint {
|
||||
idx := lo.Map(items, func(item models.Post, index int) uint {
|
||||
return item.ID
|
||||
})
|
||||
|
||||
@ -204,14 +162,14 @@ func (v *PostTypeContext) List(take int, offset int, noReact ...bool) ([]*models
|
||||
}
|
||||
|
||||
if err := database.C.Model(&models.Reaction{}).
|
||||
Select(v.ColumnName+"_id as post_id, symbol, COUNT(id) as count").
|
||||
Where(v.ColumnName+"_id IN (?)", idx).
|
||||
Select("post_id as post_id, symbol, COUNT(id) as count").
|
||||
Where("post_id IN (?)", idx).
|
||||
Group("post_id, symbol").
|
||||
Scan(&reactions).Error; err != nil {
|
||||
return items, err
|
||||
}
|
||||
|
||||
itemMap := lo.SliceToMap(items, func(item *models.Feed) (uint, *models.Feed) {
|
||||
itemMap := lo.SliceToMap(items, func(item models.Post) (uint, models.Post) {
|
||||
return item.ID, item
|
||||
})
|
||||
|
||||
@ -230,73 +188,34 @@ func (v *PostTypeContext) List(take int, offset int, noReact ...bool) ([]*models
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
var attachments []struct {
|
||||
models.Attachment
|
||||
|
||||
PostID uint `json:"post_id"`
|
||||
}
|
||||
|
||||
itemMap := lo.SliceToMap(items, func(item *models.Feed) (uint, *models.Feed) {
|
||||
return item.ID, item
|
||||
})
|
||||
|
||||
idx := lo.Map(items, func(item *models.Feed, index int) uint {
|
||||
return item.ID
|
||||
})
|
||||
|
||||
if err := database.C.
|
||||
Model(&models.Attachment{}).
|
||||
Select(v.ColumnName+"_id as post_id, *").
|
||||
Where(v.ColumnName+"_id IN (?)", idx).
|
||||
Scan(&attachments).Error; err != nil {
|
||||
return items, err
|
||||
}
|
||||
|
||||
list := map[uint][]models.Attachment{}
|
||||
for _, info := range attachments {
|
||||
list[info.PostID] = append(list[info.PostID], info.Attachment)
|
||||
}
|
||||
|
||||
for k, v := range list {
|
||||
if post, ok := itemMap[k]; ok {
|
||||
post.Attachments = v
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return items, nil
|
||||
}
|
||||
|
||||
func MapCategoriesAndTags[T models.PostInterface](item T) (T, error) {
|
||||
func InitPostCategoriesAndTags(item models.Post) (models.Post, error) {
|
||||
var err error
|
||||
categories := item.GetCategories()
|
||||
for idx, category := range categories {
|
||||
categories[idx], err = GetCategory(category.Alias)
|
||||
for idx, category := range item.Categories {
|
||||
item.Categories[idx], err = GetCategory(category.Alias)
|
||||
if err != nil {
|
||||
return item, err
|
||||
}
|
||||
}
|
||||
item.SetCategories(categories)
|
||||
tags := item.GetHashtags()
|
||||
for idx, tag := range tags {
|
||||
tags[idx], err = GetTagOrCreate(tag.Alias, tag.Name)
|
||||
for idx, tag := range item.Tags {
|
||||
item.Tags[idx], err = GetTagOrCreate(tag.Alias, tag.Name)
|
||||
if err != nil {
|
||||
return item, err
|
||||
}
|
||||
}
|
||||
item.SetHashtags(tags)
|
||||
return item, nil
|
||||
}
|
||||
|
||||
func NewPost[T models.PostInterface](item T) (T, error) {
|
||||
item, err := MapCategoriesAndTags(item)
|
||||
func NewPost(user models.Account, item models.Post) (models.Post, error) {
|
||||
item, err := InitPostCategoriesAndTags(item)
|
||||
if err != nil {
|
||||
return item, err
|
||||
}
|
||||
|
||||
if item.GetRealm() != nil {
|
||||
_, err := GetRealmMember(item.GetRealm().ID, item.GetAuthor().ExternalID)
|
||||
if item.RealmID != nil {
|
||||
_, err := GetRealmMember(*item.RealmID, user.ExternalID)
|
||||
if err != nil {
|
||||
return item, fmt.Errorf("you aren't a part of that realm: %v", err)
|
||||
}
|
||||
@ -306,19 +225,20 @@ func NewPost[T models.PostInterface](item T) (T, error) {
|
||||
return item, err
|
||||
}
|
||||
|
||||
if item.GetReplyTo() != nil {
|
||||
// Notify the original poster its post has been replied
|
||||
if item.ReplyID != nil {
|
||||
go func() {
|
||||
var op models.Moment
|
||||
var op models.Post
|
||||
if err := database.C.
|
||||
Where("id = ?", item.GetReplyTo()).
|
||||
Where("id = ?", item.ReplyID).
|
||||
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())
|
||||
if op.Author.ID != user.ID {
|
||||
postUrl := fmt.Sprintf("https://%s/posts/%s", viper.GetString("domain"), item.Alias)
|
||||
err := NotifyAccount(
|
||||
op.Author,
|
||||
fmt.Sprintf("%s replied you", item.GetAuthor().Name),
|
||||
fmt.Sprintf("%s replied your post. Check it out!", item.GetAuthor().Name),
|
||||
fmt.Sprintf("%s replied you", user.Nick),
|
||||
fmt.Sprintf("%s (%s) replied your post #%s.", user.Nick, user.Name, op.Alias),
|
||||
false,
|
||||
&proto.NotifyLink{Label: "Related post", Url: postUrl},
|
||||
)
|
||||
@ -330,36 +250,11 @@ func NewPost[T models.PostInterface](item T) (T, error) {
|
||||
}()
|
||||
}
|
||||
|
||||
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
|
||||
})
|
||||
|
||||
for _, account := range accounts {
|
||||
postUrl := fmt.Sprintf("https://%s/posts/%d", viper.GetString("domain"), item.GetID())
|
||||
err := NotifyAccount(
|
||||
account,
|
||||
fmt.Sprintf("%s just posted a post", item.GetAuthor().Name),
|
||||
"Someone you followed post a brand new post. Check it out!",
|
||||
false,
|
||||
&proto.NotifyLink{Label: "Related post", Url: postUrl},
|
||||
)
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msg("An error occurred when notifying user...")
|
||||
}
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
return item, nil
|
||||
}
|
||||
|
||||
func EditPost[T models.PostInterface](item T) (T, error) {
|
||||
item, err := MapCategoriesAndTags(item)
|
||||
func EditPost(item models.Post) (models.Post, error) {
|
||||
item, err := InitPostCategoriesAndTags(item)
|
||||
if err != nil {
|
||||
return item, err
|
||||
}
|
||||
@ -369,11 +264,11 @@ func EditPost[T models.PostInterface](item T) (T, error) {
|
||||
return item, err
|
||||
}
|
||||
|
||||
func DeletePost[T models.PostInterface](item T) error {
|
||||
func DeletePost(item models.Post) error {
|
||||
return database.C.Delete(&item).Error
|
||||
}
|
||||
|
||||
func (v *PostTypeContext) React(reaction models.Reaction) (bool, models.Reaction, error) {
|
||||
func ReactPost(reaction models.Reaction) (bool, models.Reaction, error) {
|
||||
if err := database.C.Where(reaction).First(&reaction).Error; err != nil {
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return true, reaction, database.C.Save(&reaction).Error
|
||||
|
Loading…
Reference in New Issue
Block a user