♻️ Interactive v2 #1
							
								
								
									
										20
									
								
								pkg/models/feed.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										20
									
								
								pkg/models/feed.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,20 @@ | ||||
| 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"` | ||||
| 	ReactionList map[string]int64 `json:"reaction_list" gorm:"-"` | ||||
| } | ||||
| @@ -9,81 +9,18 @@ import ( | ||||
| 	"code.smartsheep.studio/hydrogen/interactive/pkg/services" | ||||
| 	"github.com/gofiber/fiber/v2" | ||||
| 	"github.com/google/uuid" | ||||
| 	"github.com/samber/lo" | ||||
| ) | ||||
|  | ||||
| func contextArticle() *services.PostTypeContext[*models.Article] { | ||||
| 	return &services.PostTypeContext[*models.Article]{ | ||||
| 		Tx:        database.C, | ||||
| 		TypeName:  "Article", | ||||
| 		CanReply:  false, | ||||
| 		CanRepost: false, | ||||
| func contextArticle() *services.PostTypeContext { | ||||
| 	return &services.PostTypeContext{ | ||||
| 		Tx:         database.C, | ||||
| 		TableName:  "articles", | ||||
| 		ColumnName: "article", | ||||
| 		CanReply:   false, | ||||
| 		CanRepost:  false, | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func getArticle(c *fiber.Ctx) error { | ||||
| 	alias := c.Params("articleId") | ||||
|  | ||||
| 	mx := contextArticle().FilterPublishedAt(time.Now()) | ||||
|  | ||||
| 	item, err := mx.GetViaAlias(alias) | ||||
| 	if err != nil { | ||||
| 		return fiber.NewError(fiber.StatusNotFound, err.Error()) | ||||
| 	} | ||||
|  | ||||
| 	item.ReactionList, err = mx.CountReactions(item.ID) | ||||
| 	if err != nil { | ||||
| 		return fiber.NewError(fiber.StatusInternalServerError, err.Error()) | ||||
| 	} | ||||
|  | ||||
| 	return c.JSON(item) | ||||
| } | ||||
|  | ||||
| func listArticle(c *fiber.Ctx) error { | ||||
| 	take := c.QueryInt("take", 0) | ||||
| 	offset := c.QueryInt("offset", 0) | ||||
| 	realmId := c.QueryInt("realmId", 0) | ||||
|  | ||||
| 	mx := contextArticle(). | ||||
| 		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()) | ||||
| 		} | ||||
| 		mx = mx.FilterAuthor(author.ID) | ||||
| 	} | ||||
|  | ||||
| 	if len(c.Query("category")) > 0 { | ||||
| 		mx = mx.FilterWithCategory(c.Query("category")) | ||||
| 	} | ||||
| 	if len(c.Query("tag")) > 0 { | ||||
| 		mx = mx.FilterWithTag(c.Query("tag")) | ||||
| 	} | ||||
|  | ||||
| 	if !c.QueryBool("reply", true) { | ||||
| 		mx = mx.FilterReply(true) | ||||
| 	} | ||||
|  | ||||
| 	count, err := mx.Count() | ||||
| 	if err != nil { | ||||
| 		return fiber.NewError(fiber.StatusInternalServerError, err.Error()) | ||||
| 	} | ||||
|  | ||||
| 	items, err := mx.List(take, offset) | ||||
| 	if err != nil { | ||||
| 		return fiber.NewError(fiber.StatusBadRequest, err.Error()) | ||||
| 	} | ||||
|  | ||||
| 	return c.JSON(fiber.Map{ | ||||
| 		"count": count, | ||||
| 		"data":  items, | ||||
| 	}) | ||||
| } | ||||
|  | ||||
| func createArticle(c *fiber.Ctx) error { | ||||
| 	user := c.Locals("principal").(models.Account) | ||||
|  | ||||
| @@ -105,8 +42,6 @@ func createArticle(c *fiber.Ctx) error { | ||||
| 		data.Alias = strings.ReplaceAll(uuid.NewString(), "-", "") | ||||
| 	} | ||||
|  | ||||
| 	mx := contextArticle() | ||||
|  | ||||
| 	item := &models.Article{ | ||||
| 		PostBase: models.PostBase{ | ||||
| 			Alias:       data.Alias, | ||||
| @@ -131,12 +66,11 @@ func createArticle(c *fiber.Ctx) error { | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	item, err := mx.New(item) | ||||
| 	if err != nil { | ||||
| 	if item, err := services.NewPost(item); err != nil { | ||||
| 		return fiber.NewError(fiber.StatusBadRequest, err.Error()) | ||||
| 	} else { | ||||
| 		return c.JSON(item) | ||||
| 	} | ||||
|  | ||||
| 	return c.JSON(item) | ||||
| } | ||||
|  | ||||
| func editArticle(c *fiber.Ctx) error { | ||||
| @@ -158,10 +92,13 @@ func editArticle(c *fiber.Ctx) error { | ||||
| 		return err | ||||
| 	} | ||||
|  | ||||
| 	mx := contextArticle().FilterAuthor(user.ID) | ||||
|  | ||||
| 	item, err := mx.Get(uint(id)) | ||||
| 	if err != nil { | ||||
| 	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()) | ||||
| 	} | ||||
|  | ||||
| @@ -174,61 +111,28 @@ func editArticle(c *fiber.Ctx) error { | ||||
| 	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(item) | ||||
| } | ||||
|  | ||||
| func reactArticle(c *fiber.Ctx) error { | ||||
| 	user := c.Locals("principal").(models.Account) | ||||
| 	id, _ := c.ParamsInt("articleId", 0) | ||||
|  | ||||
| 	var data struct { | ||||
| 		Symbol   string                  `json:"symbol" validate:"required"` | ||||
| 		Attitude models.ReactionAttitude `json:"attitude" validate:"required"` | ||||
| 	} | ||||
|  | ||||
| 	if err := BindAndValidate(c, &data); err != nil { | ||||
| 		return err | ||||
| 	} | ||||
|  | ||||
| 	mx := contextArticle() | ||||
|  | ||||
| 	item, err := mx.Get(uint(id), true) | ||||
| 	if err != nil { | ||||
| 		return fiber.NewError(fiber.StatusNotFound, err.Error()) | ||||
| 	} | ||||
|  | ||||
| 	reaction := models.Reaction{ | ||||
| 		Symbol:    data.Symbol, | ||||
| 		Attitude:  data.Attitude, | ||||
| 		AccountID: user.ID, | ||||
| 		ArticleID: &item.ID, | ||||
| 	} | ||||
|  | ||||
| 	if positive, reaction, err := mx.React(reaction); err != nil { | ||||
| 	if item, err := services.EditPost(item); err != nil { | ||||
| 		return fiber.NewError(fiber.StatusBadRequest, err.Error()) | ||||
| 	} else { | ||||
| 		return c.Status(lo.Ternary(positive, fiber.StatusCreated, fiber.StatusNoContent)).JSON(reaction) | ||||
| 		return c.JSON(item) | ||||
| 	} | ||||
|  | ||||
| } | ||||
|  | ||||
| func deleteArticle(c *fiber.Ctx) error { | ||||
| 	user := c.Locals("principal").(models.Account) | ||||
| 	id, _ := c.ParamsInt("articleId", 0) | ||||
|  | ||||
| 	mx := contextArticle().FilterAuthor(user.ID) | ||||
|  | ||||
| 	item, err := mx.Get(uint(id), true) | ||||
| 	if err != nil { | ||||
| 	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 := mx.Delete(item); err != nil { | ||||
| 	if err := services.DeletePost(item); err != nil { | ||||
| 		return fiber.NewError(fiber.StatusBadRequest, err.Error()) | ||||
| 	} | ||||
|  | ||||
|   | ||||
| @@ -10,81 +10,18 @@ import ( | ||||
| 	"code.smartsheep.studio/hydrogen/interactive/pkg/services" | ||||
| 	"github.com/gofiber/fiber/v2" | ||||
| 	"github.com/google/uuid" | ||||
| 	"github.com/samber/lo" | ||||
| ) | ||||
|  | ||||
| func contextComment() *services.PostTypeContext[*models.Comment] { | ||||
| 	return &services.PostTypeContext[*models.Comment]{ | ||||
| 		Tx:        database.C, | ||||
| 		TypeName:  "Comment", | ||||
| 		CanReply:  false, | ||||
| 		CanRepost: true, | ||||
| func contextComment() *services.PostTypeContext { | ||||
| 	return &services.PostTypeContext{ | ||||
| 		Tx:         database.C, | ||||
| 		TableName:  "comments", | ||||
| 		ColumnName: "comment", | ||||
| 		CanReply:   false, | ||||
| 		CanRepost:  true, | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func getComment(c *fiber.Ctx) error { | ||||
| 	alias := c.Params("commentId") | ||||
|  | ||||
| 	mx := contextComment().FilterPublishedAt(time.Now()) | ||||
|  | ||||
| 	item, err := mx.GetViaAlias(alias) | ||||
| 	if err != nil { | ||||
| 		return fiber.NewError(fiber.StatusNotFound, err.Error()) | ||||
| 	} | ||||
|  | ||||
| 	item.ReactionList, err = mx.CountReactions(item.ID) | ||||
| 	if err != nil { | ||||
| 		return fiber.NewError(fiber.StatusInternalServerError, err.Error()) | ||||
| 	} | ||||
|  | ||||
| 	return c.JSON(item) | ||||
| } | ||||
|  | ||||
| func listComment(c *fiber.Ctx) error { | ||||
| 	take := c.QueryInt("take", 0) | ||||
| 	offset := c.QueryInt("offset", 0) | ||||
| 	realmId := c.QueryInt("realmId", 0) | ||||
|  | ||||
| 	mx := contextComment(). | ||||
| 		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()) | ||||
| 		} | ||||
| 		mx = mx.FilterAuthor(author.ID) | ||||
| 	} | ||||
|  | ||||
| 	if len(c.Query("category")) > 0 { | ||||
| 		mx = mx.FilterWithCategory(c.Query("category")) | ||||
| 	} | ||||
| 	if len(c.Query("tag")) > 0 { | ||||
| 		mx = mx.FilterWithTag(c.Query("tag")) | ||||
| 	} | ||||
|  | ||||
| 	if !c.QueryBool("reply", true) { | ||||
| 		mx = mx.FilterReply(true) | ||||
| 	} | ||||
|  | ||||
| 	count, err := mx.Count() | ||||
| 	if err != nil { | ||||
| 		return fiber.NewError(fiber.StatusInternalServerError, err.Error()) | ||||
| 	} | ||||
|  | ||||
| 	items, err := mx.List(take, offset) | ||||
| 	if err != nil { | ||||
| 		return fiber.NewError(fiber.StatusBadRequest, err.Error()) | ||||
| 	} | ||||
|  | ||||
| 	return c.JSON(fiber.Map{ | ||||
| 		"count": count, | ||||
| 		"data":  items, | ||||
| 	}) | ||||
| } | ||||
|  | ||||
| func createComment(c *fiber.Ctx) error { | ||||
| 	user := c.Locals("principal").(models.Account) | ||||
|  | ||||
| @@ -106,8 +43,6 @@ func createComment(c *fiber.Ctx) error { | ||||
| 		data.Alias = strings.ReplaceAll(uuid.NewString(), "-", "") | ||||
| 	} | ||||
|  | ||||
| 	mx := contextComment() | ||||
|  | ||||
| 	item := &models.Comment{ | ||||
| 		PostBase: models.PostBase{ | ||||
| 			Alias:       data.Alias, | ||||
| @@ -142,7 +77,7 @@ func createComment(c *fiber.Ctx) error { | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	item, err := mx.New(item) | ||||
| 	item, err := services.NewPost(item) | ||||
| 	if err != nil { | ||||
| 		return fiber.NewError(fiber.StatusBadRequest, err.Error()) | ||||
| 	} | ||||
| @@ -167,10 +102,13 @@ func editComment(c *fiber.Ctx) error { | ||||
| 		return err | ||||
| 	} | ||||
|  | ||||
| 	mx := contextComment().FilterAuthor(user.ID) | ||||
|  | ||||
| 	item, err := mx.Get(uint(id)) | ||||
| 	if err != nil { | ||||
| 	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()) | ||||
| 	} | ||||
|  | ||||
| @@ -181,61 +119,28 @@ func editComment(c *fiber.Ctx) error { | ||||
| 	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(item) | ||||
| } | ||||
|  | ||||
| func reactComment(c *fiber.Ctx) error { | ||||
| 	user := c.Locals("principal").(models.Account) | ||||
| 	id, _ := c.ParamsInt("commentId", 0) | ||||
|  | ||||
| 	var data struct { | ||||
| 		Symbol   string                  `json:"symbol" validate:"required"` | ||||
| 		Attitude models.ReactionAttitude `json:"attitude" validate:"required"` | ||||
| 	} | ||||
|  | ||||
| 	if err := BindAndValidate(c, &data); err != nil { | ||||
| 		return err | ||||
| 	} | ||||
|  | ||||
| 	mx := contextComment() | ||||
|  | ||||
| 	item, err := mx.Get(uint(id), true) | ||||
| 	if err != nil { | ||||
| 		return fiber.NewError(fiber.StatusNotFound, err.Error()) | ||||
| 	} | ||||
|  | ||||
| 	reaction := models.Reaction{ | ||||
| 		Symbol:    data.Symbol, | ||||
| 		Attitude:  data.Attitude, | ||||
| 		AccountID: user.ID, | ||||
| 		CommentID: &item.ID, | ||||
| 	} | ||||
|  | ||||
| 	if positive, reaction, err := mx.React(reaction); err != nil { | ||||
| 	if item, err := services.EditPost(item); err != nil { | ||||
| 		return fiber.NewError(fiber.StatusBadRequest, err.Error()) | ||||
| 	} else { | ||||
| 		return c.Status(lo.Ternary(positive, fiber.StatusCreated, fiber.StatusNoContent)).JSON(reaction) | ||||
| 		return c.JSON(item) | ||||
| 	} | ||||
|  | ||||
| } | ||||
|  | ||||
| func deleteComment(c *fiber.Ctx) error { | ||||
| 	user := c.Locals("principal").(models.Account) | ||||
| 	id, _ := c.ParamsInt("commentId", 0) | ||||
|  | ||||
| 	mx := contextComment().FilterAuthor(user.ID) | ||||
|  | ||||
| 	item, err := mx.Get(uint(id), true) | ||||
| 	if err != nil { | ||||
| 	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 := mx.Delete(item); err != nil { | ||||
| 	if err := services.DeletePost(item); err != nil { | ||||
| 		return fiber.NewError(fiber.StatusBadRequest, err.Error()) | ||||
| 	} | ||||
|  | ||||
|   | ||||
| @@ -1,6 +1,5 @@ | ||||
| package server | ||||
|  | ||||
| import "C" | ||||
| import ( | ||||
| 	"code.smartsheep.studio/hydrogen/interactive/pkg/database" | ||||
| 	"code.smartsheep.studio/hydrogen/interactive/pkg/models" | ||||
| @@ -10,23 +9,6 @@ import ( | ||||
| 	"github.com/spf13/viper" | ||||
| ) | ||||
|  | ||||
| type FeedItem struct { | ||||
| 	models.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       models.Account   `json:"author" gorm:"embedded"` | ||||
| 	ReactionList map[string]int64 `json:"reaction_list"` | ||||
| } | ||||
|  | ||||
| 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" | ||||
| @@ -58,7 +40,7 @@ func listFeed(c *fiber.Ctx) error { | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	var result []*FeedItem | ||||
| 	var result []*models.Feed | ||||
|  | ||||
| 	userTable := viper.GetString("database.prefix") + "accounts" | ||||
| 	commentTable := viper.GetString("database.prefix") + "comments" | ||||
| @@ -94,15 +76,15 @@ func listFeed(c *fiber.Ctx) error { | ||||
| 		} | ||||
|  | ||||
| 		revertReaction := func(dataset string) error { | ||||
| 			itemMap := lo.SliceToMap(lo.FilterMap(result, func(item *FeedItem, index int) (*FeedItem, bool) { | ||||
| 			itemMap := lo.SliceToMap(lo.FilterMap(result, func(item *models.Feed, index int) (*models.Feed, bool) { | ||||
| 				return item, item.ModelType == dataset | ||||
| 			}), func(item *FeedItem) (uint, *FeedItem) { | ||||
| 			}), func(item *models.Feed) (uint, *models.Feed) { | ||||
| 				return item.ID, item | ||||
| 			}) | ||||
|  | ||||
| 			idx := lo.Map(lo.Filter(result, func(item *FeedItem, index int) bool { | ||||
| 			idx := lo.Map(lo.Filter(result, func(item *models.Feed, index int) bool { | ||||
| 				return item.ModelType == dataset | ||||
| 			}), func(item *FeedItem, index int) uint { | ||||
| 			}), func(item *models.Feed, index int) uint { | ||||
| 				return item.ID | ||||
| 			}) | ||||
|  | ||||
|   | ||||
| @@ -9,81 +9,18 @@ import ( | ||||
| 	"code.smartsheep.studio/hydrogen/interactive/pkg/services" | ||||
| 	"github.com/gofiber/fiber/v2" | ||||
| 	"github.com/google/uuid" | ||||
| 	"github.com/samber/lo" | ||||
| ) | ||||
|  | ||||
| func contextMoment() *services.PostTypeContext[*models.Moment] { | ||||
| 	return &services.PostTypeContext[*models.Moment]{ | ||||
| 		Tx:        database.C, | ||||
| 		TypeName:  "Moment", | ||||
| 		CanReply:  false, | ||||
| 		CanRepost: true, | ||||
| func contextMoment() *services.PostTypeContext { | ||||
| 	return &services.PostTypeContext{ | ||||
| 		Tx:         database.C, | ||||
| 		TableName:  "moments", | ||||
| 		ColumnName: "moment", | ||||
| 		CanReply:   false, | ||||
| 		CanRepost:  true, | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func getMoment(c *fiber.Ctx) error { | ||||
| 	alias := c.Params("momentId") | ||||
|  | ||||
| 	mx := contextMoment().FilterPublishedAt(time.Now()) | ||||
|  | ||||
| 	item, err := mx.GetViaAlias(alias) | ||||
| 	if err != nil { | ||||
| 		return fiber.NewError(fiber.StatusNotFound, err.Error()) | ||||
| 	} | ||||
|  | ||||
| 	item.ReactionList, err = mx.CountReactions(item.ID) | ||||
| 	if err != nil { | ||||
| 		return fiber.NewError(fiber.StatusInternalServerError, err.Error()) | ||||
| 	} | ||||
|  | ||||
| 	return c.JSON(item) | ||||
| } | ||||
|  | ||||
| func listMoment(c *fiber.Ctx) error { | ||||
| 	take := c.QueryInt("take", 0) | ||||
| 	offset := c.QueryInt("offset", 0) | ||||
| 	realmId := c.QueryInt("realmId", 0) | ||||
|  | ||||
| 	mx := contextMoment(). | ||||
| 		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()) | ||||
| 		} | ||||
| 		mx = mx.FilterAuthor(author.ID) | ||||
| 	} | ||||
|  | ||||
| 	if len(c.Query("category")) > 0 { | ||||
| 		mx = mx.FilterWithCategory(c.Query("category")) | ||||
| 	} | ||||
| 	if len(c.Query("tag")) > 0 { | ||||
| 		mx = mx.FilterWithTag(c.Query("tag")) | ||||
| 	} | ||||
|  | ||||
| 	if !c.QueryBool("reply", true) { | ||||
| 		mx = mx.FilterReply(true) | ||||
| 	} | ||||
|  | ||||
| 	count, err := mx.Count() | ||||
| 	if err != nil { | ||||
| 		return fiber.NewError(fiber.StatusInternalServerError, err.Error()) | ||||
| 	} | ||||
|  | ||||
| 	items, err := mx.List(take, offset) | ||||
| 	if err != nil { | ||||
| 		return fiber.NewError(fiber.StatusBadRequest, err.Error()) | ||||
| 	} | ||||
|  | ||||
| 	return c.JSON(fiber.Map{ | ||||
| 		"count": count, | ||||
| 		"data":  items, | ||||
| 	}) | ||||
| } | ||||
|  | ||||
| func createMoment(c *fiber.Ctx) error { | ||||
| 	user := c.Locals("principal").(models.Account) | ||||
|  | ||||
| @@ -104,8 +41,6 @@ func createMoment(c *fiber.Ctx) error { | ||||
| 		data.Alias = strings.ReplaceAll(uuid.NewString(), "-", "") | ||||
| 	} | ||||
|  | ||||
| 	mx := contextMoment() | ||||
|  | ||||
| 	item := &models.Moment{ | ||||
| 		PostBase: models.PostBase{ | ||||
| 			Alias:       data.Alias, | ||||
| @@ -140,7 +75,7 @@ func createMoment(c *fiber.Ctx) error { | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	item, err := mx.New(item) | ||||
| 	item, err := services.NewPost(item) | ||||
| 	if err != nil { | ||||
| 		return fiber.NewError(fiber.StatusBadRequest, err.Error()) | ||||
| 	} | ||||
| @@ -165,10 +100,13 @@ func editMoment(c *fiber.Ctx) error { | ||||
| 		return err | ||||
| 	} | ||||
|  | ||||
| 	mx := contextMoment().FilterAuthor(user.ID) | ||||
|  | ||||
| 	item, err := mx.Get(uint(id)) | ||||
| 	if err != nil { | ||||
| 	var item *models.Moment | ||||
| 	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()) | ||||
| 	} | ||||
|  | ||||
| @@ -179,61 +117,28 @@ func editMoment(c *fiber.Ctx) error { | ||||
| 	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(item) | ||||
| } | ||||
|  | ||||
| func reactMoment(c *fiber.Ctx) error { | ||||
| 	user := c.Locals("principal").(models.Account) | ||||
| 	id, _ := c.ParamsInt("momentId", 0) | ||||
|  | ||||
| 	var data struct { | ||||
| 		Symbol   string                  `json:"symbol" validate:"required"` | ||||
| 		Attitude models.ReactionAttitude `json:"attitude" validate:"required"` | ||||
| 	} | ||||
|  | ||||
| 	if err := BindAndValidate(c, &data); err != nil { | ||||
| 		return err | ||||
| 	} | ||||
|  | ||||
| 	mx := contextMoment() | ||||
|  | ||||
| 	item, err := mx.Get(uint(id), true) | ||||
| 	if err != nil { | ||||
| 		return fiber.NewError(fiber.StatusNotFound, err.Error()) | ||||
| 	} | ||||
|  | ||||
| 	reaction := models.Reaction{ | ||||
| 		Symbol:    data.Symbol, | ||||
| 		Attitude:  data.Attitude, | ||||
| 		AccountID: user.ID, | ||||
| 		MomentID:  &item.ID, | ||||
| 	} | ||||
|  | ||||
| 	if positive, reaction, err := mx.React(reaction); err != nil { | ||||
| 	if item, err := services.EditPost(item); err != nil { | ||||
| 		return fiber.NewError(fiber.StatusBadRequest, err.Error()) | ||||
| 	} else { | ||||
| 		return c.Status(lo.Ternary(positive, fiber.StatusCreated, fiber.StatusNoContent)).JSON(reaction) | ||||
| 		return c.JSON(item) | ||||
| 	} | ||||
|  | ||||
| } | ||||
|  | ||||
| func deleteMoment(c *fiber.Ctx) error { | ||||
| 	user := c.Locals("principal").(models.Account) | ||||
| 	id, _ := c.ParamsInt("momentId", 0) | ||||
|  | ||||
| 	mx := contextMoment().FilterAuthor(user.ID) | ||||
|  | ||||
| 	item, err := mx.Get(uint(id), true) | ||||
| 	if err != nil { | ||||
| 	var item *models.Moment | ||||
| 	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 := mx.Delete(item); err != nil { | ||||
| 	if err := services.DeletePost(item); err != nil { | ||||
| 		return fiber.NewError(fiber.StatusBadRequest, err.Error()) | ||||
| 	} | ||||
|  | ||||
|   | ||||
							
								
								
									
										126
									
								
								pkg/server/posts_api.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										126
									
								
								pkg/server/posts_api.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,126 @@ | ||||
| package server | ||||
|  | ||||
| import ( | ||||
| 	"time" | ||||
|  | ||||
| 	"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" | ||||
| ) | ||||
|  | ||||
| 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) | ||||
|  | ||||
| 	item, err := mx.GetViaAlias(alias) | ||||
| 	if err != nil { | ||||
| 		return fiber.NewError(fiber.StatusNotFound, err.Error()) | ||||
| 	} | ||||
|  | ||||
| 	item.ReactionList, err = mx.CountReactions(item.ID) | ||||
| 	if err != nil { | ||||
| 		return fiber.NewError(fiber.StatusInternalServerError, err.Error()) | ||||
| 	} | ||||
|  | ||||
| 	return c.JSON(item) | ||||
| } | ||||
|  | ||||
| func listPost(c *fiber.Ctx) error { | ||||
| 	take := c.QueryInt("take", 0) | ||||
| 	offset := c.QueryInt("offset", 0) | ||||
| 	realmId := c.QueryInt("realmId", 0) | ||||
|  | ||||
| 	mx := c.Locals(postContextKey).(*services.PostTypeContext). | ||||
| 		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()) | ||||
| 		} | ||||
| 		mx = mx.FilterAuthor(author.ID) | ||||
| 	} | ||||
|  | ||||
| 	if len(c.Query("category")) > 0 { | ||||
| 		mx = mx.FilterWithCategory(c.Query("category")) | ||||
| 	} | ||||
| 	if len(c.Query("tag")) > 0 { | ||||
| 		mx = mx.FilterWithTag(c.Query("tag")) | ||||
| 	} | ||||
|  | ||||
| 	if !c.QueryBool("reply", true) { | ||||
| 		mx = mx.FilterReply(true) | ||||
| 	} | ||||
|  | ||||
| 	count, err := mx.Count() | ||||
| 	if err != nil { | ||||
| 		return fiber.NewError(fiber.StatusInternalServerError, err.Error()) | ||||
| 	} | ||||
|  | ||||
| 	items, err := mx.List(take, offset) | ||||
| 	if err != nil { | ||||
| 		return fiber.NewError(fiber.StatusBadRequest, err.Error()) | ||||
| 	} | ||||
|  | ||||
| 	return c.JSON(fiber.Map{ | ||||
| 		"count": count, | ||||
| 		"data":  items, | ||||
| 	}) | ||||
| } | ||||
|  | ||||
| func reactPost(c *fiber.Ctx) error { | ||||
| 	user := c.Locals("principal").(models.Account) | ||||
| 	id, _ := c.ParamsInt("articleId", 0) | ||||
|  | ||||
| 	var data struct { | ||||
| 		Symbol   string                  `json:"symbol" validate:"required"` | ||||
| 		Attitude models.ReactionAttitude `json:"attitude" validate:"required"` | ||||
| 	} | ||||
|  | ||||
| 	if err := BindAndValidate(c, &data); err != nil { | ||||
| 		return err | ||||
| 	} | ||||
|  | ||||
| 	mx := c.Locals(postContextKey).(*services.PostTypeContext) | ||||
|  | ||||
| 	item, err := mx.Get(uint(id), true) | ||||
| 	if err != nil { | ||||
| 		return fiber.NewError(fiber.StatusNotFound, err.Error()) | ||||
| 	} | ||||
|  | ||||
| 	reaction := models.Reaction{ | ||||
| 		Symbol:    data.Symbol, | ||||
| 		Attitude:  data.Attitude, | ||||
| 		AccountID: user.ID, | ||||
| 		ArticleID: &item.ID, | ||||
| 	} | ||||
|  | ||||
| 	if positive, reaction, err := mx.React(reaction); err != nil { | ||||
| 		return fiber.NewError(fiber.StatusBadRequest, err.Error()) | ||||
| 	} else { | ||||
| 		return c.Status(lo.Ternary(positive, fiber.StatusCreated, fiber.StatusNoContent)).JSON(reaction) | ||||
| 	} | ||||
| } | ||||
| @@ -71,32 +71,30 @@ func NewServer() { | ||||
|  | ||||
| 		api.Get("/feed", listFeed) | ||||
|  | ||||
| 		posts := api.Group("/p/:postType").Use(useDynamicContext).Name("Dataset Universal API") | ||||
| 		{ | ||||
| 			posts.Get("/", listPost) | ||||
| 			posts.Get("/:postId", getPost) | ||||
| 			posts.Post("/:postId/react", authMiddleware, reactPost) | ||||
| 		} | ||||
|  | ||||
| 		moments := api.Group("/moments").Name("Moments API") | ||||
| 		{ | ||||
| 			moments.Get("/", listMoment) | ||||
| 			moments.Get("/:momentId", getMoment) | ||||
| 			moments.Post("/", authMiddleware, createMoment) | ||||
| 			moments.Post("/:momentId/react", authMiddleware, reactMoment) | ||||
| 			moments.Put("/:momentId", authMiddleware, editMoment) | ||||
| 			moments.Delete("/:momentId", authMiddleware, deleteMoment) | ||||
| 		} | ||||
|  | ||||
| 		articles := api.Group("/articles").Name("Articles API") | ||||
| 		articles := api.Group("/p/articles").Name("Articles API") | ||||
| 		{ | ||||
| 			articles.Get("/", listArticle) | ||||
| 			articles.Get("/:articleId", getArticle) | ||||
| 			articles.Post("/", authMiddleware, createArticle) | ||||
| 			articles.Post("/:articleId/react", authMiddleware, reactArticle) | ||||
| 			articles.Put("/:articleId", authMiddleware, editArticle) | ||||
| 			articles.Delete("/:articleId", authMiddleware, deleteArticle) | ||||
| 		} | ||||
|  | ||||
| 		comments := api.Group("/comments").Name("Comments API") | ||||
| 		comments := api.Group("/p/comments").Name("Comments API") | ||||
| 		{ | ||||
| 			comments.Get("/", listComment) | ||||
| 			comments.Get("/:commentId", getComment) | ||||
| 			comments.Post("/", authMiddleware, createComment) | ||||
| 			comments.Post("/:commentId/react", authMiddleware, reactComment) | ||||
| 			comments.Put("/:commentId", authMiddleware, editComment) | ||||
| 			comments.Delete("/:commentId", authMiddleware, deleteComment) | ||||
| 		} | ||||
|   | ||||
| @@ -1,81 +1,50 @@ | ||||
| package services | ||||
|  | ||||
| import "C" | ||||
| import ( | ||||
| 	"errors" | ||||
| 	"fmt" | ||||
| 	"time" | ||||
|  | ||||
| 	"code.smartsheep.studio/hydrogen/identity/pkg/grpc/proto" | ||||
| 	"code.smartsheep.studio/hydrogen/interactive/pkg/database" | ||||
| 	"code.smartsheep.studio/hydrogen/interactive/pkg/models" | ||||
| 	"errors" | ||||
| 	"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" | ||||
| ) | ||||
|  | ||||
| type PostTypeContext[T models.PostInterface] struct { | ||||
| type PostTypeContext struct { | ||||
| 	Tx *gorm.DB | ||||
|  | ||||
| 	TypeName  string | ||||
| 	CanReply  bool | ||||
| 	CanRepost bool | ||||
| 	TableName  string | ||||
| 	ColumnName string | ||||
| 	CanReply   bool | ||||
| 	CanRepost  bool | ||||
| } | ||||
|  | ||||
| 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(noComments ...bool) *PostTypeContext[T] { | ||||
| 	v.Tx.Preload("Author"). | ||||
| 		Preload("Attachments"). | ||||
| 		Preload("Categories"). | ||||
| 		Preload("Hashtags") | ||||
|  | ||||
| 	if len(noComments) <= 0 || !noComments[0] { | ||||
| 		v.Tx = v.Tx.Preload("Comments") | ||||
| 	} | ||||
|  | ||||
| 	if v.CanReply { | ||||
| 		v.Tx.Preload("ReplyTo") | ||||
| 	} | ||||
| 	if v.CanRepost { | ||||
| 		v.Tx.Preload("RepostTo") | ||||
| 	} | ||||
|  | ||||
| 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 (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) | ||||
| 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[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] { | ||||
| func (v *PostTypeContext) FilterPublishedAt(date time.Time) *PostTypeContext { | ||||
| 	v.Tx.Where("published_at <= ? AND published_at IS NULL", date) | ||||
| 	return v | ||||
| } | ||||
|  | ||||
| func (v *PostTypeContext[T]) FilterRealm(id uint) *PostTypeContext[T] { | ||||
| func (v *PostTypeContext) FilterRealm(id uint) *PostTypeContext { | ||||
| 	if id > 0 { | ||||
| 		v.Tx = v.Tx.Where("realm_id = ?", id) | ||||
| 	} else { | ||||
| @@ -84,12 +53,12 @@ func (v *PostTypeContext[T]) FilterRealm(id uint) *PostTypeContext[T] { | ||||
| 	return v | ||||
| } | ||||
|  | ||||
| func (v *PostTypeContext[T]) FilterAuthor(id uint) *PostTypeContext[T] { | ||||
| func (v *PostTypeContext) FilterAuthor(id uint) *PostTypeContext { | ||||
| 	v.Tx = v.Tx.Where("author_id = ?", id) | ||||
| 	return v | ||||
| } | ||||
|  | ||||
| func (v *PostTypeContext[T]) FilterReply(condition bool) *PostTypeContext[T] { | ||||
| func (v *PostTypeContext) FilterReply(condition bool) *PostTypeContext { | ||||
| 	if condition { | ||||
| 		v.Tx = v.Tx.Where("reply_id IS NOT NULL") | ||||
| 	} else { | ||||
| @@ -98,32 +67,34 @@ func (v *PostTypeContext[T]) FilterReply(condition bool) *PostTypeContext[T] { | ||||
| 	return v | ||||
| } | ||||
|  | ||||
| func (v *PostTypeContext[T]) SortCreatedAt(order string) *PostTypeContext[T] { | ||||
| func (v *PostTypeContext) SortCreatedAt(order string) *PostTypeContext { | ||||
| 	v.Tx.Order(fmt.Sprintf("created_at %s", order)) | ||||
| 	return v | ||||
| } | ||||
|  | ||||
| func (v *PostTypeContext[T]) GetViaAlias(alias string, noComments ...bool) (T, error) { | ||||
| 	var item T | ||||
| 	if err := v.Preload(noComments...).Tx.Where("alias = ?", alias).First(&item).Error; err != nil { | ||||
| func (v *PostTypeContext) GetViaAlias(alias string, noComments ...bool) (models.Feed, error) { | ||||
| 	var item models.Feed | ||||
| 	if err := v.Tx.Where("alias = ?", alias).First(&item).Error; err != nil { | ||||
| 		return item, err | ||||
| 	} | ||||
|  | ||||
| 	return item, nil | ||||
| } | ||||
|  | ||||
| func (v *PostTypeContext[T]) Get(id uint, noComments ...bool) (T, error) { | ||||
| 	var item T | ||||
| 	if err := v.Preload(noComments...).Tx.Where("id = ?", id).First(&item).Error; err != nil { | ||||
| func (v *PostTypeContext) Get(id uint, noComments ...bool) (models.Feed, error) { | ||||
| 	var item models.Feed | ||||
| 	if err := v.Tx. | ||||
| 		Select("*, ? as model_type", v.ColumnName). | ||||
| 		Where("id = ?", id).First(&item).Error; err != nil { | ||||
| 		return item, err | ||||
| 	} | ||||
|  | ||||
| 	return item, nil | ||||
| } | ||||
|  | ||||
| func (v *PostTypeContext[T]) Count() (int64, error) { | ||||
| func (v *PostTypeContext) Count() (int64, error) { | ||||
| 	var count int64 | ||||
| 	table := viper.GetString("database.prefix") + v.GetTableName(true) | ||||
| 	table := viper.GetString("database.prefix") + v.TableName | ||||
| 	if err := v.Tx.Table(table).Count(&count).Error; err != nil { | ||||
| 		return count, err | ||||
| 	} | ||||
| @@ -131,7 +102,7 @@ func (v *PostTypeContext[T]) Count() (int64, error) { | ||||
| 	return count, nil | ||||
| } | ||||
|  | ||||
| func (v *PostTypeContext[T]) CountReactions(id uint) (map[string]int64, error) { | ||||
| func (v *PostTypeContext) CountReactions(id uint) (map[string]int64, error) { | ||||
| 	var reactions []struct { | ||||
| 		Symbol string | ||||
| 		Count  int64 | ||||
| @@ -139,7 +110,7 @@ func (v *PostTypeContext[T]) CountReactions(id uint) (map[string]int64, error) { | ||||
|  | ||||
| 	if err := database.C.Model(&models.Reaction{}). | ||||
| 		Select("symbol, COUNT(id) as count"). | ||||
| 		Where(strings.ToLower(v.TypeName)+"_id = ?", id). | ||||
| 		Where(v.ColumnName+"_id = ?", id). | ||||
| 		Group("symbol"). | ||||
| 		Scan(&reactions).Error; err != nil { | ||||
| 		return map[string]int64{}, err | ||||
| @@ -148,23 +119,26 @@ func (v *PostTypeContext[T]) CountReactions(id uint) (map[string]int64, error) { | ||||
| 	return lo.SliceToMap(reactions, func(item struct { | ||||
| 		Symbol string | ||||
| 		Count  int64 | ||||
| 	}) (string, int64) { | ||||
| 	}, | ||||
| 	) (string, int64) { | ||||
| 		return item.Symbol, item.Count | ||||
| 	}), nil | ||||
| } | ||||
|  | ||||
| func (v *PostTypeContext[T]) List(take int, offset int, noReact ...bool) ([]T, error) { | ||||
| func (v *PostTypeContext) List(take int, offset int, noReact ...bool) ([]*models.Feed, error) { | ||||
| 	if take > 20 { | ||||
| 		take = 20 | ||||
| 	} | ||||
|  | ||||
| 	var items []T | ||||
| 	if err := v.Preload().Tx.Limit(take).Offset(offset).Find(&items).Error; err != nil { | ||||
| 	var items []*models.Feed | ||||
| 	if err := v.Tx. | ||||
| 		Select("*, ? as model_type", v.ColumnName). | ||||
| 		Limit(take).Offset(offset).Find(&items).Error; err != nil { | ||||
| 		return items, err | ||||
| 	} | ||||
|  | ||||
| 	idx := lo.Map(items, func(item T, index int) uint { | ||||
| 		return item.GetID() | ||||
| 	idx := lo.Map(items, func(item *models.Feed, index int) uint { | ||||
| 		return item.ID | ||||
| 	}) | ||||
|  | ||||
| 	if len(noReact) <= 0 || !noReact[0] { | ||||
| @@ -175,15 +149,15 @@ func (v *PostTypeContext[T]) List(take int, offset int, noReact ...bool) ([]T, e | ||||
| 		} | ||||
|  | ||||
| 		if err := database.C.Model(&models.Reaction{}). | ||||
| 			Select(strings.ToLower(v.TypeName)+"_id as post_id, symbol, COUNT(id) as count"). | ||||
| 			Where(strings.ToLower(v.TypeName)+"_id IN (?)", idx). | ||||
| 			Select(v.ColumnName+"_id as post_id, symbol, COUNT(id) as count"). | ||||
| 			Where(v.ColumnName+"_id IN (?)", idx). | ||||
| 			Group("post_id, symbol"). | ||||
| 			Scan(&reactions).Error; err != nil { | ||||
| 			return items, err | ||||
| 		} | ||||
|  | ||||
| 		itemMap := lo.SliceToMap(items, func(item T) (uint, T) { | ||||
| 			return item.GetID(), item | ||||
| 		itemMap := lo.SliceToMap(items, func(item *models.Feed) (uint, *models.Feed) { | ||||
| 			return item.ID, item | ||||
| 		}) | ||||
|  | ||||
| 		list := map[uint]map[string]int64{} | ||||
| @@ -196,7 +170,7 @@ func (v *PostTypeContext[T]) List(take int, offset int, noReact ...bool) ([]T, e | ||||
|  | ||||
| 		for k, v := range list { | ||||
| 			if post, ok := itemMap[k]; ok { | ||||
| 				post.SetReactionList(v) | ||||
| 				post.ReactionList = v | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
| @@ -204,7 +178,7 @@ func (v *PostTypeContext[T]) List(take int, offset int, noReact ...bool) ([]T, e | ||||
| 	return items, nil | ||||
| } | ||||
|  | ||||
| func (v *PostTypeContext[T]) MapCategoriesAndTags(item T) (T, error) { | ||||
| func MapCategoriesAndTags[T models.PostInterface](item T) (T, error) { | ||||
| 	var err error | ||||
| 	categories := item.GetCategories() | ||||
| 	for idx, category := range categories { | ||||
| @@ -225,8 +199,8 @@ func (v *PostTypeContext[T]) MapCategoriesAndTags(item T) (T, error) { | ||||
| 	return item, nil | ||||
| } | ||||
|  | ||||
| func (v *PostTypeContext[T]) New(item T) (T, error) { | ||||
| 	item, err := v.MapCategoriesAndTags(item) | ||||
| func NewPost[T models.PostInterface](item T) (T, error) { | ||||
| 	item, err := MapCategoriesAndTags(item) | ||||
| 	if err != nil { | ||||
| 		return item, err | ||||
| 	} | ||||
| @@ -294,8 +268,8 @@ func (v *PostTypeContext[T]) New(item T) (T, error) { | ||||
| 	return item, nil | ||||
| } | ||||
|  | ||||
| func (v *PostTypeContext[T]) Edit(item T) (T, error) { | ||||
| 	item, err := v.MapCategoriesAndTags(item) | ||||
| func EditPost[T models.PostInterface](item T) (T, error) { | ||||
| 	item, err := MapCategoriesAndTags(item) | ||||
| 	if err != nil { | ||||
| 		return item, err | ||||
| 	} | ||||
| @@ -305,11 +279,11 @@ func (v *PostTypeContext[T]) Edit(item T) (T, error) { | ||||
| 	return item, err | ||||
| } | ||||
|  | ||||
| func (v *PostTypeContext[T]) Delete(item T) error { | ||||
| func DeletePost[T models.PostInterface](item T) error { | ||||
| 	return database.C.Delete(&item).Error | ||||
| } | ||||
|  | ||||
| func (v *PostTypeContext[T]) React(reaction models.Reaction) (bool, models.Reaction, error) { | ||||
| func (v *PostTypeContext) React(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 | ||||
|   | ||||
| @@ -14,7 +14,7 @@ function parseContent(src: string): string { | ||||
| </script> | ||||
|  | ||||
| <style> | ||||
| .prose.prose-moment, p { | ||||
| .prose.prose-moment p { | ||||
|   margin: 0 !important; | ||||
| } | ||||
| </style> | ||||
|   | ||||
		Reference in New Issue
	
	Block a user