♻️ 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" | 	"code.smartsheep.studio/hydrogen/interactive/pkg/services" | ||||||
| 	"github.com/gofiber/fiber/v2" | 	"github.com/gofiber/fiber/v2" | ||||||
| 	"github.com/google/uuid" | 	"github.com/google/uuid" | ||||||
| 	"github.com/samber/lo" |  | ||||||
| ) | ) | ||||||
|  |  | ||||||
| func contextArticle() *services.PostTypeContext[*models.Article] { | func contextArticle() *services.PostTypeContext { | ||||||
| 	return &services.PostTypeContext[*models.Article]{ | 	return &services.PostTypeContext{ | ||||||
| 		Tx:         database.C, | 		Tx:         database.C, | ||||||
| 		TypeName:  "Article", | 		TableName:  "articles", | ||||||
|  | 		ColumnName: "article", | ||||||
| 		CanReply:   false, | 		CanReply:   false, | ||||||
| 		CanRepost:  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 { | func createArticle(c *fiber.Ctx) error { | ||||||
| 	user := c.Locals("principal").(models.Account) | 	user := c.Locals("principal").(models.Account) | ||||||
|  |  | ||||||
| @@ -105,8 +42,6 @@ func createArticle(c *fiber.Ctx) error { | |||||||
| 		data.Alias = strings.ReplaceAll(uuid.NewString(), "-", "") | 		data.Alias = strings.ReplaceAll(uuid.NewString(), "-", "") | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	mx := contextArticle() |  | ||||||
|  |  | ||||||
| 	item := &models.Article{ | 	item := &models.Article{ | ||||||
| 		PostBase: models.PostBase{ | 		PostBase: models.PostBase{ | ||||||
| 			Alias:       data.Alias, | 			Alias:       data.Alias, | ||||||
| @@ -131,12 +66,11 @@ func createArticle(c *fiber.Ctx) error { | |||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	item, err := mx.New(item) | 	if item, err := services.NewPost(item); err != nil { | ||||||
| 	if err != nil { |  | ||||||
| 		return fiber.NewError(fiber.StatusBadRequest, err.Error()) | 		return fiber.NewError(fiber.StatusBadRequest, err.Error()) | ||||||
| 	} | 	} else { | ||||||
|  |  | ||||||
| 		return c.JSON(item) | 		return c.JSON(item) | ||||||
|  | 	} | ||||||
| } | } | ||||||
|  |  | ||||||
| func editArticle(c *fiber.Ctx) error { | func editArticle(c *fiber.Ctx) error { | ||||||
| @@ -158,10 +92,13 @@ func editArticle(c *fiber.Ctx) error { | |||||||
| 		return err | 		return err | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	mx := contextArticle().FilterAuthor(user.ID) | 	var item *models.Article | ||||||
|  | 	if err := database.C.Where(models.Article{ | ||||||
| 	item, err := mx.Get(uint(id)) | 		PostBase: models.PostBase{ | ||||||
| 	if err != nil { | 			BaseModel: models.BaseModel{ID: uint(id)}, | ||||||
|  | 			AuthorID:  user.ID, | ||||||
|  | 		}, | ||||||
|  | 	}).First(&item).Error; err != nil { | ||||||
| 		return fiber.NewError(fiber.StatusNotFound, err.Error()) | 		return fiber.NewError(fiber.StatusNotFound, err.Error()) | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| @@ -174,61 +111,28 @@ func editArticle(c *fiber.Ctx) error { | |||||||
| 	item.Categories = data.Categories | 	item.Categories = data.Categories | ||||||
| 	item.Attachments = data.Attachments | 	item.Attachments = data.Attachments | ||||||
|  |  | ||||||
| 	item, err = mx.Edit(item) | 	if item, err := services.EditPost(item); err != nil { | ||||||
| 	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 { |  | ||||||
| 		return fiber.NewError(fiber.StatusBadRequest, err.Error()) | 		return fiber.NewError(fiber.StatusBadRequest, err.Error()) | ||||||
| 	} else { | 	} else { | ||||||
| 		return c.Status(lo.Ternary(positive, fiber.StatusCreated, fiber.StatusNoContent)).JSON(reaction) | 		return c.JSON(item) | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| } | } | ||||||
|  |  | ||||||
| func deleteArticle(c *fiber.Ctx) error { | func deleteArticle(c *fiber.Ctx) error { | ||||||
| 	user := c.Locals("principal").(models.Account) | 	user := c.Locals("principal").(models.Account) | ||||||
| 	id, _ := c.ParamsInt("articleId", 0) | 	id, _ := c.ParamsInt("articleId", 0) | ||||||
|  |  | ||||||
| 	mx := contextArticle().FilterAuthor(user.ID) | 	var item *models.Article | ||||||
|  | 	if err := database.C.Where(models.Article{ | ||||||
| 	item, err := mx.Get(uint(id), true) | 		PostBase: models.PostBase{ | ||||||
| 	if err != nil { | 			BaseModel: models.BaseModel{ID: uint(id)}, | ||||||
|  | 			AuthorID:  user.ID, | ||||||
|  | 		}, | ||||||
|  | 	}).First(&item).Error; err != nil { | ||||||
| 		return fiber.NewError(fiber.StatusNotFound, err.Error()) | 		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()) | 		return fiber.NewError(fiber.StatusBadRequest, err.Error()) | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
|   | |||||||
| @@ -10,81 +10,18 @@ import ( | |||||||
| 	"code.smartsheep.studio/hydrogen/interactive/pkg/services" | 	"code.smartsheep.studio/hydrogen/interactive/pkg/services" | ||||||
| 	"github.com/gofiber/fiber/v2" | 	"github.com/gofiber/fiber/v2" | ||||||
| 	"github.com/google/uuid" | 	"github.com/google/uuid" | ||||||
| 	"github.com/samber/lo" |  | ||||||
| ) | ) | ||||||
|  |  | ||||||
| func contextComment() *services.PostTypeContext[*models.Comment] { | func contextComment() *services.PostTypeContext { | ||||||
| 	return &services.PostTypeContext[*models.Comment]{ | 	return &services.PostTypeContext{ | ||||||
| 		Tx:         database.C, | 		Tx:         database.C, | ||||||
| 		TypeName:  "Comment", | 		TableName:  "comments", | ||||||
|  | 		ColumnName: "comment", | ||||||
| 		CanReply:   false, | 		CanReply:   false, | ||||||
| 		CanRepost:  true, | 		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 { | func createComment(c *fiber.Ctx) error { | ||||||
| 	user := c.Locals("principal").(models.Account) | 	user := c.Locals("principal").(models.Account) | ||||||
|  |  | ||||||
| @@ -106,8 +43,6 @@ func createComment(c *fiber.Ctx) error { | |||||||
| 		data.Alias = strings.ReplaceAll(uuid.NewString(), "-", "") | 		data.Alias = strings.ReplaceAll(uuid.NewString(), "-", "") | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	mx := contextComment() |  | ||||||
|  |  | ||||||
| 	item := &models.Comment{ | 	item := &models.Comment{ | ||||||
| 		PostBase: models.PostBase{ | 		PostBase: models.PostBase{ | ||||||
| 			Alias:       data.Alias, | 			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 { | 	if err != nil { | ||||||
| 		return fiber.NewError(fiber.StatusBadRequest, err.Error()) | 		return fiber.NewError(fiber.StatusBadRequest, err.Error()) | ||||||
| 	} | 	} | ||||||
| @@ -167,10 +102,13 @@ func editComment(c *fiber.Ctx) error { | |||||||
| 		return err | 		return err | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	mx := contextComment().FilterAuthor(user.ID) | 	var item *models.Comment | ||||||
|  | 	if err := database.C.Where(models.Comment{ | ||||||
| 	item, err := mx.Get(uint(id)) | 		PostBase: models.PostBase{ | ||||||
| 	if err != nil { | 			BaseModel: models.BaseModel{ID: uint(id)}, | ||||||
|  | 			AuthorID:  user.ID, | ||||||
|  | 		}, | ||||||
|  | 	}).First(&item).Error; err != nil { | ||||||
| 		return fiber.NewError(fiber.StatusNotFound, err.Error()) | 		return fiber.NewError(fiber.StatusNotFound, err.Error()) | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| @@ -181,61 +119,28 @@ func editComment(c *fiber.Ctx) error { | |||||||
| 	item.Categories = data.Categories | 	item.Categories = data.Categories | ||||||
| 	item.Attachments = data.Attachments | 	item.Attachments = data.Attachments | ||||||
|  |  | ||||||
| 	item, err = mx.Edit(item) | 	if item, err := services.EditPost(item); err != nil { | ||||||
| 	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 { |  | ||||||
| 		return fiber.NewError(fiber.StatusBadRequest, err.Error()) | 		return fiber.NewError(fiber.StatusBadRequest, err.Error()) | ||||||
| 	} else { | 	} else { | ||||||
| 		return c.Status(lo.Ternary(positive, fiber.StatusCreated, fiber.StatusNoContent)).JSON(reaction) | 		return c.JSON(item) | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| } | } | ||||||
|  |  | ||||||
| func deleteComment(c *fiber.Ctx) error { | func deleteComment(c *fiber.Ctx) error { | ||||||
| 	user := c.Locals("principal").(models.Account) | 	user := c.Locals("principal").(models.Account) | ||||||
| 	id, _ := c.ParamsInt("commentId", 0) | 	id, _ := c.ParamsInt("commentId", 0) | ||||||
|  |  | ||||||
| 	mx := contextComment().FilterAuthor(user.ID) | 	var item *models.Comment | ||||||
|  | 	if err := database.C.Where(models.Comment{ | ||||||
| 	item, err := mx.Get(uint(id), true) | 		PostBase: models.PostBase{ | ||||||
| 	if err != nil { | 			BaseModel: models.BaseModel{ID: uint(id)}, | ||||||
|  | 			AuthorID:  user.ID, | ||||||
|  | 		}, | ||||||
|  | 	}).First(&item).Error; err != nil { | ||||||
| 		return fiber.NewError(fiber.StatusNotFound, err.Error()) | 		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()) | 		return fiber.NewError(fiber.StatusBadRequest, err.Error()) | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
|   | |||||||
| @@ -1,6 +1,5 @@ | |||||||
| package server | package server | ||||||
|  |  | ||||||
| import "C" |  | ||||||
| import ( | import ( | ||||||
| 	"code.smartsheep.studio/hydrogen/interactive/pkg/database" | 	"code.smartsheep.studio/hydrogen/interactive/pkg/database" | ||||||
| 	"code.smartsheep.studio/hydrogen/interactive/pkg/models" | 	"code.smartsheep.studio/hydrogen/interactive/pkg/models" | ||||||
| @@ -10,23 +9,6 @@ import ( | |||||||
| 	"github.com/spf13/viper" | 	"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 ( | const ( | ||||||
| 	queryArticle = "id, created_at, updated_at, alias, title, NULL as content, description, realm_id, author_id, 'article' as model_type" | 	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" | 	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" | 	userTable := viper.GetString("database.prefix") + "accounts" | ||||||
| 	commentTable := viper.GetString("database.prefix") + "comments" | 	commentTable := viper.GetString("database.prefix") + "comments" | ||||||
| @@ -94,15 +76,15 @@ func listFeed(c *fiber.Ctx) error { | |||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		revertReaction := func(dataset string) 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 | 				return item, item.ModelType == dataset | ||||||
| 			}), func(item *FeedItem) (uint, *FeedItem) { | 			}), func(item *models.Feed) (uint, *models.Feed) { | ||||||
| 				return item.ID, item | 				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 | 				return item.ModelType == dataset | ||||||
| 			}), func(item *FeedItem, index int) uint { | 			}), func(item *models.Feed, index int) uint { | ||||||
| 				return item.ID | 				return item.ID | ||||||
| 			}) | 			}) | ||||||
|  |  | ||||||
|   | |||||||
| @@ -9,81 +9,18 @@ import ( | |||||||
| 	"code.smartsheep.studio/hydrogen/interactive/pkg/services" | 	"code.smartsheep.studio/hydrogen/interactive/pkg/services" | ||||||
| 	"github.com/gofiber/fiber/v2" | 	"github.com/gofiber/fiber/v2" | ||||||
| 	"github.com/google/uuid" | 	"github.com/google/uuid" | ||||||
| 	"github.com/samber/lo" |  | ||||||
| ) | ) | ||||||
|  |  | ||||||
| func contextMoment() *services.PostTypeContext[*models.Moment] { | func contextMoment() *services.PostTypeContext { | ||||||
| 	return &services.PostTypeContext[*models.Moment]{ | 	return &services.PostTypeContext{ | ||||||
| 		Tx:         database.C, | 		Tx:         database.C, | ||||||
| 		TypeName:  "Moment", | 		TableName:  "moments", | ||||||
|  | 		ColumnName: "moment", | ||||||
| 		CanReply:   false, | 		CanReply:   false, | ||||||
| 		CanRepost:  true, | 		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 { | func createMoment(c *fiber.Ctx) error { | ||||||
| 	user := c.Locals("principal").(models.Account) | 	user := c.Locals("principal").(models.Account) | ||||||
|  |  | ||||||
| @@ -104,8 +41,6 @@ func createMoment(c *fiber.Ctx) error { | |||||||
| 		data.Alias = strings.ReplaceAll(uuid.NewString(), "-", "") | 		data.Alias = strings.ReplaceAll(uuid.NewString(), "-", "") | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	mx := contextMoment() |  | ||||||
|  |  | ||||||
| 	item := &models.Moment{ | 	item := &models.Moment{ | ||||||
| 		PostBase: models.PostBase{ | 		PostBase: models.PostBase{ | ||||||
| 			Alias:       data.Alias, | 			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 { | 	if err != nil { | ||||||
| 		return fiber.NewError(fiber.StatusBadRequest, err.Error()) | 		return fiber.NewError(fiber.StatusBadRequest, err.Error()) | ||||||
| 	} | 	} | ||||||
| @@ -165,10 +100,13 @@ func editMoment(c *fiber.Ctx) error { | |||||||
| 		return err | 		return err | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	mx := contextMoment().FilterAuthor(user.ID) | 	var item *models.Moment | ||||||
|  | 	if err := database.C.Where(models.Comment{ | ||||||
| 	item, err := mx.Get(uint(id)) | 		PostBase: models.PostBase{ | ||||||
| 	if err != nil { | 			BaseModel: models.BaseModel{ID: uint(id)}, | ||||||
|  | 			AuthorID:  user.ID, | ||||||
|  | 		}, | ||||||
|  | 	}).First(&item).Error; err != nil { | ||||||
| 		return fiber.NewError(fiber.StatusNotFound, err.Error()) | 		return fiber.NewError(fiber.StatusNotFound, err.Error()) | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| @@ -179,61 +117,28 @@ func editMoment(c *fiber.Ctx) error { | |||||||
| 	item.Categories = data.Categories | 	item.Categories = data.Categories | ||||||
| 	item.Attachments = data.Attachments | 	item.Attachments = data.Attachments | ||||||
|  |  | ||||||
| 	item, err = mx.Edit(item) | 	if item, err := services.EditPost(item); err != nil { | ||||||
| 	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 { |  | ||||||
| 		return fiber.NewError(fiber.StatusBadRequest, err.Error()) | 		return fiber.NewError(fiber.StatusBadRequest, err.Error()) | ||||||
| 	} else { | 	} else { | ||||||
| 		return c.Status(lo.Ternary(positive, fiber.StatusCreated, fiber.StatusNoContent)).JSON(reaction) | 		return c.JSON(item) | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| } | } | ||||||
|  |  | ||||||
| func deleteMoment(c *fiber.Ctx) error { | func deleteMoment(c *fiber.Ctx) error { | ||||||
| 	user := c.Locals("principal").(models.Account) | 	user := c.Locals("principal").(models.Account) | ||||||
| 	id, _ := c.ParamsInt("momentId", 0) | 	id, _ := c.ParamsInt("momentId", 0) | ||||||
|  |  | ||||||
| 	mx := contextMoment().FilterAuthor(user.ID) | 	var item *models.Moment | ||||||
|  | 	if err := database.C.Where(models.Comment{ | ||||||
| 	item, err := mx.Get(uint(id), true) | 		PostBase: models.PostBase{ | ||||||
| 	if err != nil { | 			BaseModel: models.BaseModel{ID: uint(id)}, | ||||||
|  | 			AuthorID:  user.ID, | ||||||
|  | 		}, | ||||||
|  | 	}).First(&item).Error; err != nil { | ||||||
| 		return fiber.NewError(fiber.StatusNotFound, err.Error()) | 		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()) | 		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) | 		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 := api.Group("/moments").Name("Moments API") | ||||||
| 		{ | 		{ | ||||||
| 			moments.Get("/", listMoment) |  | ||||||
| 			moments.Get("/:momentId", getMoment) |  | ||||||
| 			moments.Post("/", authMiddleware, createMoment) | 			moments.Post("/", authMiddleware, createMoment) | ||||||
| 			moments.Post("/:momentId/react", authMiddleware, reactMoment) |  | ||||||
| 			moments.Put("/:momentId", authMiddleware, editMoment) | 			moments.Put("/:momentId", authMiddleware, editMoment) | ||||||
| 			moments.Delete("/:momentId", authMiddleware, deleteMoment) | 			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("/", authMiddleware, createArticle) | ||||||
| 			articles.Post("/:articleId/react", authMiddleware, reactArticle) |  | ||||||
| 			articles.Put("/:articleId", authMiddleware, editArticle) | 			articles.Put("/:articleId", authMiddleware, editArticle) | ||||||
| 			articles.Delete("/:articleId", authMiddleware, deleteArticle) | 			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("/", authMiddleware, createComment) | ||||||
| 			comments.Post("/:commentId/react", authMiddleware, reactComment) |  | ||||||
| 			comments.Put("/:commentId", authMiddleware, editComment) | 			comments.Put("/:commentId", authMiddleware, editComment) | ||||||
| 			comments.Delete("/:commentId", authMiddleware, deleteComment) | 			comments.Delete("/:commentId", authMiddleware, deleteComment) | ||||||
| 		} | 		} | ||||||
|   | |||||||
| @@ -1,81 +1,50 @@ | |||||||
| package services | package services | ||||||
|  |  | ||||||
| import "C" |  | ||||||
| import ( | import ( | ||||||
|  | 	"errors" | ||||||
|  | 	"fmt" | ||||||
|  | 	"time" | ||||||
|  |  | ||||||
| 	"code.smartsheep.studio/hydrogen/identity/pkg/grpc/proto" | 	"code.smartsheep.studio/hydrogen/identity/pkg/grpc/proto" | ||||||
| 	"code.smartsheep.studio/hydrogen/interactive/pkg/database" | 	"code.smartsheep.studio/hydrogen/interactive/pkg/database" | ||||||
| 	"code.smartsheep.studio/hydrogen/interactive/pkg/models" | 	"code.smartsheep.studio/hydrogen/interactive/pkg/models" | ||||||
| 	"errors" |  | ||||||
| 	"fmt" |  | ||||||
| 	pluralize "github.com/gertd/go-pluralize" |  | ||||||
| 	"github.com/rs/zerolog/log" | 	"github.com/rs/zerolog/log" | ||||||
| 	"github.com/samber/lo" | 	"github.com/samber/lo" | ||||||
| 	"github.com/spf13/viper" | 	"github.com/spf13/viper" | ||||||
| 	"gorm.io/gorm" | 	"gorm.io/gorm" | ||||||
| 	"strings" |  | ||||||
| 	"time" |  | ||||||
| ) | ) | ||||||
|  |  | ||||||
| type PostTypeContext[T models.PostInterface] struct { | type PostTypeContext struct { | ||||||
| 	Tx *gorm.DB | 	Tx *gorm.DB | ||||||
|  |  | ||||||
| 	TypeName  string | 	TableName  string | ||||||
|  | 	ColumnName string | ||||||
| 	CanReply   bool | 	CanReply   bool | ||||||
| 	CanRepost  bool | 	CanRepost  bool | ||||||
| } | } | ||||||
|  |  | ||||||
| var pluralizeHelper = pluralize.NewClient() | func (v *PostTypeContext) FilterWithCategory(alias string) *PostTypeContext { | ||||||
|  | 	name := v.ColumnName | ||||||
| func (v *PostTypeContext[T]) GetTableName(plural ...bool) string { | 	v.Tx.Joins(fmt.Sprintf("JOIN %s_categories ON %s.id = %s_categories.%s_id", name, v.TableName, name, name)). | ||||||
| 	if len(plural) <= 0 || !plural[0] { | 		Joins(fmt.Sprintf("JOIN %s_categories ON %s_categories.id = %s_categories.category_id", name, name, name)). | ||||||
| 		return strings.ToLower(v.TypeName) | 		Where(name+"_categories.alias = ?", alias) | ||||||
| 	} 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") |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	return v | 	return v | ||||||
| } | } | ||||||
|  |  | ||||||
| func (v *PostTypeContext[T]) FilterWithCategory(alias string) *PostTypeContext[T] { | func (v *PostTypeContext) FilterWithTag(alias string) *PostTypeContext { | ||||||
| 	table := v.GetTableName() | 	name := v.ColumnName | ||||||
| 	v.Tx.Joins(fmt.Sprintf("JOIN %s_categories ON %s.id = %s_categories.%s_id", table, v.GetTableName(true), table, v.GetTableName())). | 	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_categories ON %s_categories.id = %s_categories.category_id", table, table, table)). | 		Joins(fmt.Sprintf("JOIN %s_tags ON %s_tags.id = %s_tags.category_id", name, name, name)). | ||||||
| 		Where(table+"_categories.alias = ?", alias) | 		Where(name+"_tags.alias = ?", alias) | ||||||
| 	return v | 	return v | ||||||
| } | } | ||||||
|  |  | ||||||
| func (v *PostTypeContext[T]) FilterWithTag(alias string) *PostTypeContext[T] { | func (v *PostTypeContext) FilterPublishedAt(date time.Time) *PostTypeContext { | ||||||
| 	table := v.GetTableName() |  | ||||||
| 	v.Tx.Joins(fmt.Sprintf("JOIN %s_tags ON %s.id = %s_tags.%s_id", table, v.GetTableName(true), table, v.GetTableName())). |  | ||||||
| 		Joins(fmt.Sprintf("JOIN %s_tags ON %s_tags.id = %s_tags.category_id", table, table, table)). |  | ||||||
| 		Where(table+"_tags.alias = ?", alias) |  | ||||||
| 	return v |  | ||||||
| } |  | ||||||
|  |  | ||||||
| func (v *PostTypeContext[T]) FilterPublishedAt(date time.Time) *PostTypeContext[T] { |  | ||||||
| 	v.Tx.Where("published_at <= ? AND published_at IS NULL", date) | 	v.Tx.Where("published_at <= ? AND published_at IS NULL", date) | ||||||
| 	return v | 	return v | ||||||
| } | } | ||||||
|  |  | ||||||
| func (v *PostTypeContext[T]) FilterRealm(id uint) *PostTypeContext[T] { | func (v *PostTypeContext) FilterRealm(id uint) *PostTypeContext { | ||||||
| 	if id > 0 { | 	if id > 0 { | ||||||
| 		v.Tx = v.Tx.Where("realm_id = ?", id) | 		v.Tx = v.Tx.Where("realm_id = ?", id) | ||||||
| 	} else { | 	} else { | ||||||
| @@ -84,12 +53,12 @@ func (v *PostTypeContext[T]) FilterRealm(id uint) *PostTypeContext[T] { | |||||||
| 	return v | 	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) | 	v.Tx = v.Tx.Where("author_id = ?", id) | ||||||
| 	return v | 	return v | ||||||
| } | } | ||||||
|  |  | ||||||
| func (v *PostTypeContext[T]) FilterReply(condition bool) *PostTypeContext[T] { | func (v *PostTypeContext) FilterReply(condition bool) *PostTypeContext { | ||||||
| 	if condition { | 	if condition { | ||||||
| 		v.Tx = v.Tx.Where("reply_id IS NOT NULL") | 		v.Tx = v.Tx.Where("reply_id IS NOT NULL") | ||||||
| 	} else { | 	} else { | ||||||
| @@ -98,32 +67,34 @@ func (v *PostTypeContext[T]) FilterReply(condition bool) *PostTypeContext[T] { | |||||||
| 	return v | 	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)) | 	v.Tx.Order(fmt.Sprintf("created_at %s", order)) | ||||||
| 	return v | 	return v | ||||||
| } | } | ||||||
|  |  | ||||||
| func (v *PostTypeContext[T]) GetViaAlias(alias string, noComments ...bool) (T, error) { | func (v *PostTypeContext) GetViaAlias(alias string, noComments ...bool) (models.Feed, error) { | ||||||
| 	var item T | 	var item models.Feed | ||||||
| 	if err := v.Preload(noComments...).Tx.Where("alias = ?", alias).First(&item).Error; err != nil { | 	if err := v.Tx.Where("alias = ?", alias).First(&item).Error; err != nil { | ||||||
| 		return item, err | 		return item, err | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	return item, nil | 	return item, nil | ||||||
| } | } | ||||||
|  |  | ||||||
| func (v *PostTypeContext[T]) Get(id uint, noComments ...bool) (T, error) { | func (v *PostTypeContext) Get(id uint, noComments ...bool) (models.Feed, error) { | ||||||
| 	var item T | 	var item models.Feed | ||||||
| 	if err := v.Preload(noComments...).Tx.Where("id = ?", id).First(&item).Error; err != nil { | 	if err := v.Tx. | ||||||
|  | 		Select("*, ? as model_type", v.ColumnName). | ||||||
|  | 		Where("id = ?", id).First(&item).Error; err != nil { | ||||||
| 		return item, err | 		return item, err | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	return item, nil | 	return item, nil | ||||||
| } | } | ||||||
|  |  | ||||||
| func (v *PostTypeContext[T]) Count() (int64, error) { | func (v *PostTypeContext) Count() (int64, error) { | ||||||
| 	var count int64 | 	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 { | 	if err := v.Tx.Table(table).Count(&count).Error; err != nil { | ||||||
| 		return count, err | 		return count, err | ||||||
| 	} | 	} | ||||||
| @@ -131,7 +102,7 @@ func (v *PostTypeContext[T]) Count() (int64, error) { | |||||||
| 	return count, nil | 	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 { | 	var reactions []struct { | ||||||
| 		Symbol string | 		Symbol string | ||||||
| 		Count  int64 | 		Count  int64 | ||||||
| @@ -139,7 +110,7 @@ func (v *PostTypeContext[T]) CountReactions(id uint) (map[string]int64, error) { | |||||||
|  |  | ||||||
| 	if err := database.C.Model(&models.Reaction{}). | 	if err := database.C.Model(&models.Reaction{}). | ||||||
| 		Select("symbol, COUNT(id) as count"). | 		Select("symbol, COUNT(id) as count"). | ||||||
| 		Where(strings.ToLower(v.TypeName)+"_id = ?", id). | 		Where(v.ColumnName+"_id = ?", id). | ||||||
| 		Group("symbol"). | 		Group("symbol"). | ||||||
| 		Scan(&reactions).Error; err != nil { | 		Scan(&reactions).Error; err != nil { | ||||||
| 		return map[string]int64{}, err | 		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 { | 	return lo.SliceToMap(reactions, func(item struct { | ||||||
| 		Symbol string | 		Symbol string | ||||||
| 		Count  int64 | 		Count  int64 | ||||||
| 	}) (string, int64) { | 	}, | ||||||
|  | 	) (string, int64) { | ||||||
| 		return item.Symbol, item.Count | 		return item.Symbol, item.Count | ||||||
| 	}), nil | 	}), 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 { | 	if take > 20 { | ||||||
| 		take = 20 | 		take = 20 | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	var items []T | 	var items []*models.Feed | ||||||
| 	if err := v.Preload().Tx.Limit(take).Offset(offset).Find(&items).Error; err != nil { | 	if err := v.Tx. | ||||||
|  | 		Select("*, ? as model_type", v.ColumnName). | ||||||
|  | 		Limit(take).Offset(offset).Find(&items).Error; err != nil { | ||||||
| 		return items, err | 		return items, err | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	idx := lo.Map(items, func(item T, index int) uint { | 	idx := lo.Map(items, func(item *models.Feed, index int) uint { | ||||||
| 		return item.GetID() | 		return item.ID | ||||||
| 	}) | 	}) | ||||||
|  |  | ||||||
| 	if len(noReact) <= 0 || !noReact[0] { | 	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{}). | 		if err := database.C.Model(&models.Reaction{}). | ||||||
| 			Select(strings.ToLower(v.TypeName)+"_id as post_id, symbol, COUNT(id) as count"). | 			Select(v.ColumnName+"_id as post_id, symbol, COUNT(id) as count"). | ||||||
| 			Where(strings.ToLower(v.TypeName)+"_id IN (?)", idx). | 			Where(v.ColumnName+"_id IN (?)", idx). | ||||||
| 			Group("post_id, symbol"). | 			Group("post_id, symbol"). | ||||||
| 			Scan(&reactions).Error; err != nil { | 			Scan(&reactions).Error; err != nil { | ||||||
| 			return items, err | 			return items, err | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		itemMap := lo.SliceToMap(items, func(item T) (uint, T) { | 		itemMap := lo.SliceToMap(items, func(item *models.Feed) (uint, *models.Feed) { | ||||||
| 			return item.GetID(), item | 			return item.ID, item | ||||||
| 		}) | 		}) | ||||||
|  |  | ||||||
| 		list := map[uint]map[string]int64{} | 		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 { | 		for k, v := range list { | ||||||
| 			if post, ok := itemMap[k]; ok { | 			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 | 	return items, nil | ||||||
| } | } | ||||||
|  |  | ||||||
| func (v *PostTypeContext[T]) MapCategoriesAndTags(item T) (T, error) { | func MapCategoriesAndTags[T models.PostInterface](item T) (T, error) { | ||||||
| 	var err error | 	var err error | ||||||
| 	categories := item.GetCategories() | 	categories := item.GetCategories() | ||||||
| 	for idx, category := range categories { | 	for idx, category := range categories { | ||||||
| @@ -225,8 +199,8 @@ func (v *PostTypeContext[T]) MapCategoriesAndTags(item T) (T, error) { | |||||||
| 	return item, nil | 	return item, nil | ||||||
| } | } | ||||||
|  |  | ||||||
| func (v *PostTypeContext[T]) New(item T) (T, error) { | func NewPost[T models.PostInterface](item T) (T, error) { | ||||||
| 	item, err := v.MapCategoriesAndTags(item) | 	item, err := MapCategoriesAndTags(item) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		return item, err | 		return item, err | ||||||
| 	} | 	} | ||||||
| @@ -294,8 +268,8 @@ func (v *PostTypeContext[T]) New(item T) (T, error) { | |||||||
| 	return item, nil | 	return item, nil | ||||||
| } | } | ||||||
|  |  | ||||||
| func (v *PostTypeContext[T]) Edit(item T) (T, error) { | func EditPost[T models.PostInterface](item T) (T, error) { | ||||||
| 	item, err := v.MapCategoriesAndTags(item) | 	item, err := MapCategoriesAndTags(item) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		return item, err | 		return item, err | ||||||
| 	} | 	} | ||||||
| @@ -305,11 +279,11 @@ func (v *PostTypeContext[T]) Edit(item T) (T, error) { | |||||||
| 	return item, err | 	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 | 	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 err := database.C.Where(reaction).First(&reaction).Error; err != nil { | ||||||
| 		if errors.Is(err, gorm.ErrRecordNotFound) { | 		if errors.Is(err, gorm.ErrRecordNotFound) { | ||||||
| 			return true, reaction, database.C.Save(&reaction).Error | 			return true, reaction, database.C.Save(&reaction).Error | ||||||
|   | |||||||
| @@ -14,7 +14,7 @@ function parseContent(src: string): string { | |||||||
| </script> | </script> | ||||||
|  |  | ||||||
| <style> | <style> | ||||||
| .prose.prose-moment, p { | .prose.prose-moment p { | ||||||
|   margin: 0 !important; |   margin: 0 !important; | ||||||
| } | } | ||||||
| </style> | </style> | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user