diff --git a/pkg/internal/database/migrator.go b/pkg/internal/database/migrator.go index 2ba081e..0b45570 100644 --- a/pkg/internal/database/migrator.go +++ b/pkg/internal/database/migrator.go @@ -11,7 +11,6 @@ var AutoMaintainRange = []any{ &models.Category{}, &models.Tag{}, &models.Post{}, - &models.Article{}, &models.Reaction{}, } diff --git a/pkg/internal/models/articles.go b/pkg/internal/models/articles.go deleted file mode 100644 index 779b4ab..0000000 --- a/pkg/internal/models/articles.go +++ /dev/null @@ -1,31 +0,0 @@ -package models - -import ( - "time" - - "gorm.io/datatypes" -) - -type Article struct { - BaseModel - - Alias string `json:"alias" gorm:"uniqueIndex"` - Title string `json:"title"` - Description string `json:"description"` - Content string `json:"content"` - Language string `json:"language"` - Tags []Tag `json:"tags" gorm:"many2many:article_tags"` - Categories []Category `json:"categories" gorm:"many2many:article_categories"` - Reactions []Reaction `json:"reactions"` - Attachments datatypes.JSONSlice[uint] `json:"attachments"` - RealmID *uint `json:"realm_id"` - Realm *Realm `json:"realm"` - - IsDraft bool `json:"is_draft"` - PublishedAt *time.Time `json:"published_at"` - - AuthorID uint `json:"author_id"` - Author Account `json:"author"` - - Metric PostMetric `json:"metric" gorm:"-"` -} diff --git a/pkg/internal/models/categories.go b/pkg/internal/models/categories.go index 30605f7..4cc0d66 100644 --- a/pkg/internal/models/categories.go +++ b/pkg/internal/models/categories.go @@ -3,19 +3,17 @@ package models type Tag struct { BaseModel - Alias string `json:"alias" gorm:"uniqueIndex" validate:"lowercase"` - Name string `json:"name"` - Description string `json:"description"` - Posts []Post `json:"posts" gorm:"many2many:post_tags"` - Articles []Article `json:"articles" gorm:"many2many:article_tags"` + Alias string `json:"alias" gorm:"uniqueIndex" validate:"lowercase"` + Name string `json:"name"` + Description string `json:"description"` + Posts []Post `json:"posts" gorm:"many2many:post_tags"` } type Category struct { BaseModel - Alias string `json:"alias" gorm:"uniqueIndex" validate:"lowercase,alphanum"` - Name string `json:"name"` - Description string `json:"description"` - Posts []Post `json:"posts" gorm:"many2many:post_categories"` - Articles []Article `json:"articles" gorm:"many2many:article_categories"` + Alias string `json:"alias" gorm:"uniqueIndex" validate:"lowercase,alphanum"` + Name string `json:"name"` + Description string `json:"description"` + Posts []Post `json:"posts" gorm:"many2many:post_categories"` } diff --git a/pkg/internal/models/reactions.go b/pkg/internal/models/reactions.go index 98231c5..da1762a 100644 --- a/pkg/internal/models/reactions.go +++ b/pkg/internal/models/reactions.go @@ -21,6 +21,5 @@ type Reaction struct { Attitude ReactionAttitude `json:"attitude"` PostID *uint `json:"post_id"` - ArticleID *uint `json:"article_id"` AccountID uint `json:"account_id"` } diff --git a/pkg/internal/server/api/articles_api.go b/pkg/internal/server/api/articles_api.go deleted file mode 100644 index 8b5ef1f..0000000 --- a/pkg/internal/server/api/articles_api.go +++ /dev/null @@ -1,281 +0,0 @@ -package api - -import ( - "fmt" - "strings" - "time" - - "git.solsynth.dev/hydrogen/interactive/pkg/internal/database" - "git.solsynth.dev/hydrogen/interactive/pkg/internal/gap" - "git.solsynth.dev/hydrogen/interactive/pkg/internal/models" - "git.solsynth.dev/hydrogen/interactive/pkg/internal/server/exts" - "git.solsynth.dev/hydrogen/interactive/pkg/internal/services" - "github.com/gofiber/fiber/v2" - "github.com/google/uuid" - "github.com/samber/lo" -) - -func getArticle(c *fiber.Ctx) error { - alias := c.Params("article") - - item, err := services.GetArticleWithAlias(services.FilterPostDraft(database.C), alias) - if err != nil { - return fiber.NewError(fiber.StatusNotFound, err.Error()) - } - - item.Metric.ReactionCount = services.CountArticleReactions(item.ID) - item.Metric.ReactionList, err = services.ListResourceReactions(database.C.Where("article_id = ?", 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) - - tx := services.FilterPostDraft(database.C) - if realmId > 0 { - if realm, err := services.GetRealmWithExtID(uint(realmId)); err != nil { - return fiber.NewError(fiber.StatusBadRequest, fmt.Sprintf("realm was not found: %v", err)) - } else { - tx = services.FilterArticleWithRealm(tx, realm.ID) - } - } - - if len(c.Query("authorId")) > 0 { - var author models.Account - if err := database.C.Where(&models.Account{Name: c.Query("authorId")}).First(&author).Error; err != nil { - return fiber.NewError(fiber.StatusNotFound, err.Error()) - } - tx = tx.Where("author_id = ?", author.ID) - } - - if len(c.Query("category")) > 0 { - tx = services.FilterArticleWithCategory(tx, c.Query("category")) - } - if len(c.Query("tag")) > 0 { - tx = services.FilterArticleWithTag(tx, c.Query("tag")) - } - - counTx := tx - count, err := services.CountArticle(counTx) - if err != nil { - return fiber.NewError(fiber.StatusInternalServerError, err.Error()) - } - - items, err := services.ListArticle(tx, take, offset) - if err != nil { - return fiber.NewError(fiber.StatusBadRequest, err.Error()) - } - - return c.JSON(fiber.Map{ - "count": count, - "data": items, - }) -} - -func listDraftArticle(c *fiber.Ctx) error { - take := c.QueryInt("take", 0) - offset := c.QueryInt("offset", 0) - - if err := gap.H.EnsureAuthenticated(c); err != nil { - return err - } - user := c.Locals("user").(models.Account) - - tx := services.FilterArticleWithAuthorDraft(database.C, user.ID) - - count, err := services.CountArticle(tx) - if err != nil { - return fiber.NewError(fiber.StatusInternalServerError, err.Error()) - } - - items, err := services.ListArticle(tx, take, offset, true) - 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 { - if err := gap.H.EnsureGrantedPerm(c, "CreateArticles", true); err != nil { - return err - } - user := c.Locals("user").(models.Account) - - var data struct { - Alias string `json:"alias"` - Title string `json:"title" validate:"required"` - Description string `json:"description"` - Content string `json:"content"` - Tags []models.Tag `json:"tags"` - Categories []models.Category `json:"categories"` - Attachments []uint `json:"attachments"` - IsDraft bool `json:"is_draft"` - PublishedAt *time.Time `json:"published_at"` - RealmAlias string `json:"realm"` - } - - if err := exts.BindAndValidate(c, &data); err != nil { - return err - } else if len(data.Alias) == 0 { - data.Alias = strings.ReplaceAll(uuid.NewString(), "-", "") - } - - for _, attachment := range data.Attachments { - if !services.CheckAttachmentByIDExists(attachment, "i.attachment") { - return fiber.NewError(fiber.StatusBadRequest, fmt.Sprintf("attachment %d not found", attachment)) - } - } - - item := models.Article{ - Alias: data.Alias, - Title: data.Title, - Description: data.Description, - Content: data.Content, - IsDraft: data.IsDraft, - PublishedAt: data.PublishedAt, - AuthorID: user.ID, - Tags: data.Tags, - Categories: data.Categories, - Attachments: data.Attachments, - } - - if len(data.RealmAlias) > 0 { - if realm, err := services.GetRealmWithAlias(data.RealmAlias); err != nil { - return fiber.NewError(fiber.StatusBadRequest, err.Error()) - } else if _, err := services.GetRealmMember(realm.ExternalID, user.ExternalID); err != nil { - return fiber.NewError(fiber.StatusBadRequest, fmt.Sprintf("you aren't a part of related realm: %v", err)) - } else { - item.RealmID = &realm.ID - } - } - - item, err := services.NewArticle(user, item) - if err != nil { - return fiber.NewError(fiber.StatusBadRequest, err.Error()) - } - - return c.JSON(item) -} - -func editArticle(c *fiber.Ctx) error { - id, _ := c.ParamsInt("articleId", 0) - if err := gap.H.EnsureAuthenticated(c); err != nil { - return err - } - user := c.Locals("user").(models.Account) - - var data struct { - Alias string `json:"alias"` - Title string `json:"title"` - Description string `json:"description"` - Content string `json:"content"` - IsDraft bool `json:"is_draft"` - PublishedAt *time.Time `json:"published_at"` - Tags []models.Tag `json:"tags"` - Categories []models.Category `json:"categories"` - Attachments []uint `json:"attachments"` - } - - if err := exts.BindAndValidate(c, &data); err != nil { - return err - } - - var item models.Article - if err := database.C.Where(models.Article{ - BaseModel: models.BaseModel{ID: uint(id)}, - AuthorID: user.ID, - }).First(&item).Error; err != nil { - return fiber.NewError(fiber.StatusNotFound, err.Error()) - } - - for _, attachment := range data.Attachments { - if !services.CheckAttachmentByIDExists(attachment, "i.attachment") { - return fiber.NewError(fiber.StatusBadRequest, fmt.Sprintf("attachment %d not found", attachment)) - } - } - - item.Alias = data.Alias - item.Title = data.Title - item.Description = data.Description - item.Content = data.Content - item.IsDraft = data.IsDraft - item.PublishedAt = data.PublishedAt - item.Tags = data.Tags - item.Categories = data.Categories - item.Attachments = data.Attachments - - if item, err := services.EditArticle(item); err != nil { - return fiber.NewError(fiber.StatusBadRequest, err.Error()) - } else { - return c.JSON(item) - } -} - -func deleteArticle(c *fiber.Ctx) error { - if err := gap.H.EnsureAuthenticated(c); err != nil { - return err - } - user := c.Locals("user").(models.Account) - id, _ := c.ParamsInt("articleId", 0) - - var item models.Article - if err := database.C.Where(models.Article{ - BaseModel: models.BaseModel{ID: uint(id)}, - AuthorID: user.ID, - }).First(&item).Error; err != nil { - return fiber.NewError(fiber.StatusNotFound, err.Error()) - } - - if err := services.DeleteArticle(item); err != nil { - return fiber.NewError(fiber.StatusBadRequest, err.Error()) - } - - return c.SendStatus(fiber.StatusOK) -} - -func reactArticle(c *fiber.Ctx) error { - if err := gap.H.EnsureAuthenticated(c); err != nil { - return err - } - user := c.Locals("user").(models.Account) - - var data struct { - Symbol string `json:"symbol"` - Attitude models.ReactionAttitude `json:"attitude"` - } - - if err := exts.BindAndValidate(c, &data); err != nil { - return err - } - - reaction := models.Reaction{ - Symbol: data.Symbol, - Attitude: data.Attitude, - AccountID: user.ID, - } - - alias := c.Params("article") - - var res models.Article - if err := database.C.Where("alias = ?", alias).Select("id").First(&res).Error; err != nil { - return fiber.NewError(fiber.StatusBadRequest, fmt.Sprintf("unable to find article to react: %v", err)) - } else { - reaction.ArticleID = &res.ID - } - - if positive, reaction, err := services.ReactArticle(user, reaction); err != nil { - return fiber.NewError(fiber.StatusBadRequest, err.Error()) - } else { - return c.Status(lo.Ternary(positive, fiber.StatusCreated, fiber.StatusNoContent)).JSON(reaction) - } -} diff --git a/pkg/internal/server/api/feed_api.go b/pkg/internal/server/api/feed_api.go index b6ab05e..2264a9e 100644 --- a/pkg/internal/server/api/feed_api.go +++ b/pkg/internal/server/api/feed_api.go @@ -2,7 +2,6 @@ package api import ( "fmt" - "sort" "time" "git.solsynth.dev/hydrogen/interactive/pkg/internal/database" @@ -25,14 +24,12 @@ func listFeed(c *fiber.Ctx) error { realmId := c.QueryInt("realmId", 0) postTx := services.FilterPostDraft(database.C) - articleTx := services.FilterArticleDraft(database.C) if realmId > 0 { if realm, err := services.GetRealmWithExtID(uint(realmId)); err != nil { return fiber.NewError(fiber.StatusBadRequest, fmt.Sprintf("realm was not found: %v", err)) } else { postTx = services.FilterPostWithRealm(postTx, realm.ID) - articleTx = services.FilterArticleWithRealm(articleTx, realm.ID) } } @@ -42,45 +39,33 @@ func listFeed(c *fiber.Ctx) error { return fiber.NewError(fiber.StatusNotFound, err.Error()) } postTx = postTx.Where("author_id = ?", author.ID) - articleTx = articleTx.Where("author_id = ?", author.ID) } if len(c.Query("category")) > 0 { postTx = services.FilterPostWithCategory(postTx, c.Query("category")) - articleTx = services.FilterArticleWithCategory(articleTx, c.Query("category")) } if len(c.Query("tag")) > 0 { postTx = services.FilterPostWithTag(postTx, c.Query("tag")) - articleTx = services.FilterArticleWithTag(articleTx, c.Query("tag")) } postCountTx := postTx - articleCountTx := articleTx postCount, err := services.CountPost(postCountTx) if err != nil { return fiber.NewError(fiber.StatusInternalServerError, err.Error()) } - articleCount, err := services.CountArticle(articleCountTx) - if err != nil { - return fiber.NewError(fiber.StatusInternalServerError, err.Error()) - } postItems, err := services.ListPost(postTx, take, offset) if err != nil { return fiber.NewError(fiber.StatusBadRequest, err.Error()) } - articleItems, err := services.ListArticle(articleTx, take, offset) - if err != nil { - return fiber.NewError(fiber.StatusBadRequest, err.Error()) - } var feed []FeedRecord encodeToFeed := func(t string, in any, createdAt time.Time) FeedRecord { var result map[string]any raw, _ := jsoniter.Marshal(in) - jsoniter.Unmarshal(raw, &result) + _ = jsoniter.Unmarshal(raw, &result) return FeedRecord{ Type: t, @@ -93,16 +78,8 @@ func listFeed(c *fiber.Ctx) error { feed = append(feed, encodeToFeed("post", post, post.CreatedAt)) } - for _, article := range articleItems { - feed = append(feed, encodeToFeed("article", article, article.CreatedAt)) - } - - sort.Slice(feed, func(i, j int) bool { - return feed[i].CreatedAt.After(feed[j].CreatedAt) - }) - return c.JSON(fiber.Map{ - "count": postCount + articleCount, + "count": postCount, "data": feed, }) } @@ -117,35 +94,24 @@ func listDraftMixed(c *fiber.Ctx) error { user := c.Locals("user").(models.Account) postTx := services.FilterPostWithAuthorDraft(database.C, user.ID) - articleTx := services.FilterArticleWithAuthorDraft(database.C, user.ID) - postCountTx := postTx - articleCountTx := articleTx postCount, err := services.CountPost(postCountTx) if err != nil { return fiber.NewError(fiber.StatusInternalServerError, err.Error()) } - articleCount, err := services.CountArticle(articleCountTx) - if err != nil { - return fiber.NewError(fiber.StatusInternalServerError, err.Error()) - } postItems, err := services.ListPost(postTx, take, offset) if err != nil { return fiber.NewError(fiber.StatusBadRequest, err.Error()) } - articleItems, err := services.ListArticle(articleTx, take, offset) - if err != nil { - return fiber.NewError(fiber.StatusBadRequest, err.Error()) - } var feed []FeedRecord encodeToFeed := func(t string, in any, createdAt time.Time) FeedRecord { var result map[string]any raw, _ := jsoniter.Marshal(in) - jsoniter.Unmarshal(raw, &result) + _ = jsoniter.Unmarshal(raw, &result) return FeedRecord{ Type: t, @@ -158,16 +124,8 @@ func listDraftMixed(c *fiber.Ctx) error { feed = append(feed, encodeToFeed("post", post, post.CreatedAt)) } - for _, article := range articleItems { - feed = append(feed, encodeToFeed("article", article, article.CreatedAt)) - } - - sort.Slice(feed, func(i, j int) bool { - return feed[i].CreatedAt.After(feed[j].CreatedAt) - }) - return c.JSON(fiber.Map{ - "count": postCount + articleCount, + "count": postCount, "data": feed, }) } diff --git a/pkg/internal/server/api/index.go b/pkg/internal/server/api/index.go index 6f483de..21db98c 100644 --- a/pkg/internal/server/api/index.go +++ b/pkg/internal/server/api/index.go @@ -16,7 +16,6 @@ func MapAPIs(app *fiber.App, baseURL string) { { drafts.Get("/", listDraftMixed) drafts.Get("/posts", listDraftPost) - drafts.Get("/articles", listDraftArticle) } posts := api.Group("/posts").Name("Posts API") @@ -31,16 +30,6 @@ func MapAPIs(app *fiber.App, baseURL string) { posts.Get("/:post/replies", listPostReplies) } - articles := api.Group("/articles").Name("Articles API") - { - articles.Get("/", listArticle) - articles.Get("/:article", getArticle) - articles.Post("/", createArticle) - articles.Post("/:article/react", reactArticle) - articles.Put("/:articleId", editArticle) - articles.Delete("/:articleId", deleteArticle) - } - api.Get("/categories", listCategories) api.Post("/categories", newCategory) api.Put("/categories/:categoryId", editCategory) diff --git a/pkg/internal/services/articles.go b/pkg/internal/services/articles.go deleted file mode 100644 index 84835d5..0000000 --- a/pkg/internal/services/articles.go +++ /dev/null @@ -1,234 +0,0 @@ -package services - -import ( - "errors" - "fmt" - "time" - - "git.solsynth.dev/hydrogen/interactive/pkg/internal/database" - "git.solsynth.dev/hydrogen/interactive/pkg/internal/models" - "github.com/rs/zerolog/log" - "github.com/samber/lo" - "github.com/spf13/viper" - "gorm.io/gorm" -) - -func FilterArticleWithCategory(tx *gorm.DB, alias string) *gorm.DB { - prefix := viper.GetString("database.prefix") - return tx.Joins(fmt.Sprintf("JOIN %sarticle_categories ON %sarticles.id = %sarticle_categories.article_id", prefix, prefix, prefix)). - Joins(fmt.Sprintf("JOIN %scategories ON %scategories.id = %sarticle_categories.category_id", prefix, prefix, prefix)). - Where(fmt.Sprintf("%scategories.alias = ?", prefix), alias) -} - -func FilterArticleWithTag(tx *gorm.DB, alias string) *gorm.DB { - prefix := viper.GetString("database.prefix") - return tx.Joins(fmt.Sprintf("JOIN %sarticle_tags ON %sarticles.id = %sarticle_tags.article_id", prefix, prefix, prefix)). - Joins(fmt.Sprintf("JOIN %stags ON %stags.id = %sarticle_tags.tag_id", prefix, prefix, prefix)). - Where(fmt.Sprintf("%stags.alias = ?", prefix), alias) -} - -func FilterArticleWithRealm(tx *gorm.DB, id uint) *gorm.DB { - if id > 0 { - return tx.Where("realm_id = ?", id) - } else { - return tx.Where("realm_id IS NULL") - } -} - -func FilterArticleWithPublishedAt(tx *gorm.DB, date time.Time) *gorm.DB { - return tx.Where("published_at <= ? OR published_at IS NULL", date) -} - -func FilterArticleWithAuthorDraft(tx *gorm.DB, uid uint) *gorm.DB { - return tx.Where("author_id = ? AND is_draft = ?", uid, true) -} - -func FilterArticleDraft(tx *gorm.DB) *gorm.DB { - return tx.Where("is_draft = ? OR is_draft IS NULL", false) -} - -func GetArticleWithAlias(tx *gorm.DB, alias string, ignoreLimitation ...bool) (models.Article, error) { - if len(ignoreLimitation) == 0 || !ignoreLimitation[0] { - tx = FilterArticleWithPublishedAt(tx, time.Now()) - } - - var item models.Article - if err := tx. - Where("alias = ?", alias). - Preload("Tags"). - Preload("Categories"). - Preload("Realm"). - Preload("Author"). - First(&item).Error; err != nil { - return item, err - } - - return item, nil -} - -func GetArticle(tx *gorm.DB, id uint, ignoreLimitation ...bool) (models.Article, error) { - if len(ignoreLimitation) == 0 || !ignoreLimitation[0] { - tx = FilterArticleWithPublishedAt(tx, time.Now()) - } - - var item models.Article - if err := tx. - Where("id = ?", id). - Preload("Tags"). - Preload("Categories"). - Preload("Realm"). - Preload("Author"). - First(&item).Error; err != nil { - return item, err - } - - return item, nil -} - -func CountArticle(tx *gorm.DB) (int64, error) { - var count int64 - if err := tx.Model(&models.Article{}).Count(&count).Error; err != nil { - return count, err - } - - return count, nil -} - -func CountArticleReactions(id uint) int64 { - var count int64 - if err := database.C.Model(&models.Reaction{}). - Where("article_id = ?", id). - Count(&count).Error; err != nil { - return 0 - } - - return count -} - -func ListArticle(tx *gorm.DB, take int, offset int, noReact ...bool) ([]*models.Article, error) { - if take > 100 { - take = 100 - } - - var items []*models.Article - if err := tx. - Limit(take).Offset(offset). - Order("created_at DESC"). - Preload("Tags"). - Preload("Categories"). - Preload("Realm"). - Preload("Author"). - Find(&items).Error; err != nil { - return items, err - } - - idx := lo.Map(items, func(item *models.Article, index int) uint { - return item.ID - }) - - // Load reactions - if len(noReact) <= 0 || !noReact[0] { - if mapping, err := BatchListResourceReactions(database.C.Where("article_id IN ?", idx), "article_id"); err != nil { - return items, err - } else { - itemMap := lo.SliceToMap(items, func(item *models.Article) (uint, *models.Article) { - return item.ID, item - }) - - for k, v := range mapping { - if post, ok := itemMap[k]; ok { - post.Metric = models.PostMetric{ - ReactionList: v, - } - } - } - } - } - - return items, nil -} - -func EnsureArticleCategoriesAndTags(item models.Article) (models.Article, error) { - var err error - for idx, category := range item.Categories { - item.Categories[idx], err = GetCategory(category.Alias) - if err != nil { - return item, err - } - } - for idx, tag := range item.Tags { - item.Tags[idx], err = GetTagOrCreate(tag.Alias, tag.Name) - if err != nil { - return item, err - } - } - return item, nil -} - -func NewArticle(user models.Account, item models.Article) (models.Article, error) { - item.Language = DetectLanguage(&item.Content) - - item, err := EnsureArticleCategoriesAndTags(item) - if err != nil { - return item, err - } - - if item.RealmID != nil { - _, err := GetRealmMember(*item.RealmID, user.ExternalID) - if err != nil { - return item, fmt.Errorf("you aren't a part of that realm: %v", err) - } - } - - if err := database.C.Save(&item).Error; err != nil { - return item, err - } - - return item, nil -} - -func EditArticle(item models.Article) (models.Article, error) { - item.Language = DetectLanguage(&item.Content) - item, err := EnsureArticleCategoriesAndTags(item) - if err != nil { - return item, err - } - - err = database.C.Save(&item).Error - - return item, err -} - -func DeleteArticle(item models.Article) error { - return database.C.Delete(&item).Error -} - -func ReactArticle(user models.Account, reaction models.Reaction) (bool, models.Reaction, error) { - if err := database.C.Where(reaction).First(&reaction).Error; err != nil { - if errors.Is(err, gorm.ErrRecordNotFound) { - var op models.Article - if err := database.C. - Where("id = ?", reaction.ArticleID). - Preload("Author"). - First(&op).Error; err == nil { - if op.Author.ID != user.ID { - err = NotifyPosterAccount( - op.Author, - "Article got reacted", - fmt.Sprintf("%s (%s) reacted your article a %s", user.Nick, user.Name, reaction.Symbol), - lo.ToPtr(fmt.Sprintf("%s reacted your article", user.Nick)), - ) - if err != nil { - log.Error().Err(err).Msg("An error occurred when notifying user...") - } - } - } - - return true, reaction, database.C.Save(&reaction).Error - } else { - return true, reaction, err - } - } else { - return false, reaction, database.C.Delete(&reaction).Error - } -}