♻️ Interactive v2 #1
							
								
								
									
										46
									
								
								.air.toml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										46
									
								
								.air.toml
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,46 @@ | ||||
| root = "." | ||||
| testdata_dir = "testdata" | ||||
| tmp_dir = "dist" | ||||
|  | ||||
| [build] | ||||
|   args_bin = [] | ||||
|   bin = "./dist/server" | ||||
|   cmd = "go build -o ./dist/server ./pkg/cmd/main.go" | ||||
|   delay = 1000 | ||||
|   exclude_dir = ["assets", "tmp", "vendor", "testdata", "pkg/views"] | ||||
|   exclude_file = [] | ||||
|   exclude_regex = ["_test.go"] | ||||
|   exclude_unchanged = false | ||||
|   follow_symlink = false | ||||
|   full_bin = "" | ||||
|   include_dir = [] | ||||
|   include_ext = ["go", "tpl", "tmpl", "html"] | ||||
|   include_file = [] | ||||
|   kill_delay = "0s" | ||||
|   log = "build-errors.log" | ||||
|   poll = false | ||||
|   poll_interval = 0 | ||||
|   post_cmd = [] | ||||
|   pre_cmd = [] | ||||
|   rerun = false | ||||
|   rerun_delay = 500 | ||||
|   send_interrupt = false | ||||
|   stop_on_error = false | ||||
|  | ||||
| [color] | ||||
|   app = "" | ||||
|   build = "yellow" | ||||
|   main = "magenta" | ||||
|   runner = "green" | ||||
|   watcher = "cyan" | ||||
|  | ||||
| [log] | ||||
|   main_only = false | ||||
|   time = false | ||||
|  | ||||
| [misc] | ||||
|   clean_on_exit = false | ||||
|  | ||||
| [screen] | ||||
|   clear_on_rebuild = false | ||||
|   keep_scroll = true | ||||
							
								
								
									
										3
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										3
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							| @@ -1 +1,4 @@ | ||||
| /uploads | ||||
| /dist | ||||
|  | ||||
| .DS_Store | ||||
|   | ||||
| @@ -22,6 +22,37 @@ func contextComment() *services.PostTypeContext { | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func listComment(c *fiber.Ctx) error { | ||||
| 	take := c.QueryInt("take", 0) | ||||
| 	offset := c.QueryInt("offset", 0) | ||||
| 	noReact := c.QueryBool("noReact", false) | ||||
|  | ||||
| 	alias := c.Params("postId") | ||||
|  | ||||
| 	mx := c.Locals(postContextKey).(*services.PostTypeContext). | ||||
| 		FilterPublishedAt(time.Now()) | ||||
|  | ||||
| 	item, err := mx.GetViaAlias(alias) | ||||
| 	if err != nil { | ||||
| 		return fiber.NewError(fiber.StatusNotFound, err.Error()) | ||||
| 	} | ||||
|  | ||||
| 	data, err := mx.ListComment(item.ID, take, offset, noReact) | ||||
| 	if err != nil { | ||||
| 		return fiber.NewError(fiber.StatusInternalServerError, err.Error()) | ||||
| 	} | ||||
|  | ||||
| 	count, err := mx.CountComment(item.ID) | ||||
| 	if err != nil { | ||||
| 		return fiber.NewError(fiber.StatusInternalServerError, err.Error()) | ||||
| 	} | ||||
|  | ||||
| 	return c.JSON(fiber.Map{ | ||||
| 		"count": count, | ||||
| 		"data":  data, | ||||
| 	}) | ||||
| } | ||||
|  | ||||
| func createComment(c *fiber.Ctx) error { | ||||
| 	user := c.Locals("principal").(models.Account) | ||||
|  | ||||
| @@ -32,8 +63,6 @@ func createComment(c *fiber.Ctx) error { | ||||
| 		Categories  []models.Category   `json:"categories"` | ||||
| 		Attachments []models.Attachment `json:"attachments"` | ||||
| 		PublishedAt *time.Time          `json:"published_at"` | ||||
| 		ArticleID   *uint               `json:"article_id"` | ||||
| 		MomentID    *uint               `json:"moment_id"` | ||||
| 		ReplyTo     uint                `json:"reply_to"` | ||||
| 	} | ||||
|  | ||||
| @@ -55,13 +84,29 @@ func createComment(c *fiber.Ctx) error { | ||||
| 		Content:    data.Content, | ||||
| 	} | ||||
|  | ||||
| 	if data.ArticleID == nil && data.MomentID == nil { | ||||
| 	postType := c.Params("postType") | ||||
| 	alias := c.Params("postId") | ||||
|  | ||||
| 	var err error | ||||
| 	var res models.Feed | ||||
|  | ||||
| 	switch postType { | ||||
| 	case "moments": | ||||
| 		err = database.C.Model(&models.Moment{}).Where("alias = ?", alias).Select("id").First(&res).Error | ||||
| 	case "articles": | ||||
| 		err = database.C.Model(&models.Article{}).Where("alias = ?", alias).Select("id").First(&res).Error | ||||
| 	default: | ||||
| 		return fiber.NewError(fiber.StatusBadRequest, "comment must belongs to a resource") | ||||
| 	} | ||||
| 	if data.ArticleID != nil { | ||||
| 		var article models.Article | ||||
| 		if err := database.C.Where("id = ?", data.ArticleID).First(&article).Error; err != nil { | ||||
| 			return fiber.NewError(fiber.StatusBadRequest, fmt.Sprintf("belongs to resource was not found: %v", err)) | ||||
|  | ||||
| 	if err != nil { | ||||
| 		return fiber.NewError(fiber.StatusBadRequest, fmt.Sprintf("belongs to resource was not found: %v", err)) | ||||
| 	} else { | ||||
| 		switch postType { | ||||
| 		case "moments": | ||||
| 			item.MomentID = &res.ID | ||||
| 		case "articles": | ||||
| 			item.ArticleID = &res.ID | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| @@ -77,12 +122,11 @@ func createComment(c *fiber.Ctx) error { | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	item, err := services.NewPost(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 editComment(c *fiber.Ctx) error { | ||||
|   | ||||
| @@ -1,9 +1,10 @@ | ||||
| package server | ||||
|  | ||||
| import ( | ||||
| 	"fmt" | ||||
|  | ||||
| 	"code.smartsheep.studio/hydrogen/interactive/pkg/database" | ||||
| 	"code.smartsheep.studio/hydrogen/interactive/pkg/models" | ||||
| 	"fmt" | ||||
| 	"github.com/gofiber/fiber/v2" | ||||
| 	"github.com/samber/lo" | ||||
| 	"github.com/spf13/viper" | ||||
|   | ||||
| @@ -31,7 +31,8 @@ func useDynamicContext(c *fiber.Ctx) error { | ||||
| func getPost(c *fiber.Ctx) error { | ||||
| 	alias := c.Params("postId") | ||||
|  | ||||
| 	mx := c.Locals(postContextKey).(*services.PostTypeContext) | ||||
| 	mx := c.Locals(postContextKey).(*services.PostTypeContext). | ||||
| 		FilterPublishedAt(time.Now()) | ||||
|  | ||||
| 	item, err := mx.GetViaAlias(alias) | ||||
| 	if err != nil { | ||||
|   | ||||
| @@ -76,6 +76,8 @@ func NewServer() { | ||||
| 			posts.Get("/", listPost) | ||||
| 			posts.Get("/:postId", getPost) | ||||
| 			posts.Post("/:postId/react", authMiddleware, reactPost) | ||||
| 			posts.Get("/:postId/comments", listComment) | ||||
| 			posts.Post("/:postId/comments", authMiddleware, createComment) | ||||
| 		} | ||||
|  | ||||
| 		moments := api.Group("/moments").Name("Moments API") | ||||
| @@ -94,7 +96,6 @@ func NewServer() { | ||||
|  | ||||
| 		comments := api.Group("/p/comments").Name("Comments API") | ||||
| 		{ | ||||
| 			comments.Post("/", authMiddleware, createComment) | ||||
| 			comments.Put("/:commentId", authMiddleware, editComment) | ||||
| 			comments.Delete("/:commentId", authMiddleware, deleteComment) | ||||
| 		} | ||||
|   | ||||
							
								
								
									
										82
									
								
								pkg/services/comments.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										82
									
								
								pkg/services/comments.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,82 @@ | ||||
| package services | ||||
|  | ||||
| import ( | ||||
| 	"fmt" | ||||
| 	"time" | ||||
|  | ||||
| 	"code.smartsheep.studio/hydrogen/interactive/pkg/database" | ||||
| 	"code.smartsheep.studio/hydrogen/interactive/pkg/models" | ||||
| 	"github.com/samber/lo" | ||||
| 	"github.com/spf13/viper" | ||||
| ) | ||||
|  | ||||
| func (v *PostTypeContext) ListComment(id uint, take int, offset int, noReact ...bool) ([]*models.Feed, error) { | ||||
| 	if take > 20 { | ||||
| 		take = 20 | ||||
| 	} | ||||
|  | ||||
| 	var items []*models.Feed | ||||
| 	table := viper.GetString("database.prefix") + "comments" | ||||
| 	userTable := viper.GetString("database.prefix") + "accounts" | ||||
| 	if err := v.Tx. | ||||
| 		Table(table). | ||||
| 		Select("*, ? as model_type", "comments"). | ||||
| 		Where(v.ColumnName+"_id = ?", id). | ||||
| 		Joins(fmt.Sprintf("INNER JOIN %s as author ON author_id = author.id", userTable)). | ||||
| 		Limit(take).Offset(offset).Find(&items).Error; err != nil { | ||||
| 		return items, err | ||||
| 	} | ||||
|  | ||||
| 	idx := lo.Map(items, func(item *models.Feed, index int) uint { | ||||
| 		return item.ID | ||||
| 	}) | ||||
|  | ||||
| 	if len(noReact) <= 0 || !noReact[0] { | ||||
| 		var reactions []struct { | ||||
| 			PostID uint | ||||
| 			Symbol string | ||||
| 			Count  int64 | ||||
| 		} | ||||
|  | ||||
| 		if err := database.C.Model(&models.Reaction{}). | ||||
| 			Select("comment_id as post_id, symbol, COUNT(id) as count"). | ||||
| 			Where("comment_id IN (?)", idx). | ||||
| 			Group("post_id, symbol"). | ||||
| 			Scan(&reactions).Error; err != nil { | ||||
| 			return items, err | ||||
| 		} | ||||
|  | ||||
| 		itemMap := lo.SliceToMap(items, func(item *models.Feed) (uint, *models.Feed) { | ||||
| 			return item.ID, item | ||||
| 		}) | ||||
|  | ||||
| 		list := map[uint]map[string]int64{} | ||||
| 		for _, info := range reactions { | ||||
| 			if _, ok := list[info.PostID]; !ok { | ||||
| 				list[info.PostID] = make(map[string]int64) | ||||
| 			} | ||||
| 			list[info.PostID][info.Symbol] = info.Count | ||||
| 		} | ||||
|  | ||||
| 		for k, v := range list { | ||||
| 			if post, ok := itemMap[k]; ok { | ||||
| 				post.ReactionList = v | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	return items, nil | ||||
| } | ||||
|  | ||||
| func (v *PostTypeContext) CountComment(id uint) (int64, error) { | ||||
| 	var count int64 | ||||
| 	if err := database.C. | ||||
| 		Model(&models.Comment{}). | ||||
| 		Where(v.ColumnName+"_id = ?", id). | ||||
| 		Where("published_at <= ?", time.Now()). | ||||
| 		Count(&count).Error; err != nil { | ||||
| 		return count, err | ||||
| 	} | ||||
|  | ||||
| 	return count, nil | ||||
| } | ||||
| @@ -72,9 +72,13 @@ func (v *PostTypeContext) SortCreatedAt(order string) *PostTypeContext { | ||||
| 	return v | ||||
| } | ||||
|  | ||||
| func (v *PostTypeContext) GetViaAlias(alias string, noComments ...bool) (models.Feed, error) { | ||||
| func (v *PostTypeContext) GetViaAlias(alias string) (models.Feed, error) { | ||||
| 	var item models.Feed | ||||
| 	if err := v.Tx.Where("alias = ?", alias).First(&item).Error; err != nil { | ||||
| 	table := viper.GetString("database.prefix") + v.TableName | ||||
| 	if err := v.Tx. | ||||
| 		Table(table). | ||||
| 		Where("alias = ?", alias). | ||||
| 		First(&item).Error; err != nil { | ||||
| 		return item, err | ||||
| 	} | ||||
|  | ||||
| @@ -83,7 +87,9 @@ func (v *PostTypeContext) GetViaAlias(alias string, noComments ...bool) (models. | ||||
|  | ||||
| func (v *PostTypeContext) Get(id uint, noComments ...bool) (models.Feed, error) { | ||||
| 	var item models.Feed | ||||
| 	table := viper.GetString("database.prefix") + v.TableName | ||||
| 	if err := v.Tx. | ||||
| 		Table(table). | ||||
| 		Select("*, ? as model_type", v.ColumnName). | ||||
| 		Where("id = ?", id).First(&item).Error; err != nil { | ||||
| 		return item, err | ||||
| @@ -131,7 +137,9 @@ func (v *PostTypeContext) List(take int, offset int, noReact ...bool) ([]*models | ||||
| 	} | ||||
|  | ||||
| 	var items []*models.Feed | ||||
| 	table := viper.GetString("database.prefix") + v.TableName | ||||
| 	if err := v.Tx. | ||||
| 		Table(table). | ||||
| 		Select("*, ? as model_type", v.ColumnName). | ||||
| 		Limit(take).Offset(offset).Find(&items).Error; err != nil { | ||||
| 		return items, err | ||||
|   | ||||
| @@ -24,12 +24,8 @@ | ||||
|  | ||||
|       <v-card title="Reactions" class="mt-3"> | ||||
|         <div class="px-[1rem] pb-[0.825rem] mt-[-12px]"> | ||||
|           <post-reaction | ||||
|             :item="post" | ||||
|             :model="route.params.postType" | ||||
|             :reactions="post?.reaction_list ?? {}" | ||||
|             @update="updateReactions" | ||||
|           /> | ||||
|           <post-reaction :item="post" :model="route.params.postType" :reactions="post?.reaction_list ?? {}" | ||||
|             @update="updateReactions" /> | ||||
|         </div> | ||||
|       </v-card> | ||||
|     </div> | ||||
| @@ -51,7 +47,7 @@ const route = useRoute(); | ||||
|  | ||||
| async function readPost() { | ||||
|   loading.value = true; | ||||
|   const res = await request(`/api/${route.params.postType}/${route.params.alias}?`); | ||||
|   const res = await request(`/api/p/${route.params.postType}/${route.params.alias}?`); | ||||
|   if (res.status !== 200) { | ||||
|     error.value = await res.text(); | ||||
|   } else { | ||||
|   | ||||
		Reference in New Issue
	
	Block a user