diff --git a/pkg/internal/models/posts.go b/pkg/internal/models/posts.go index bceb22d..b817745 100644 --- a/pkg/internal/models/posts.go +++ b/pkg/internal/models/posts.go @@ -11,6 +11,16 @@ const ( PostTypeArticle = "article" ) +type PostVisibilityLevel = int8 + +const ( + PostVisibilityAll = PostVisibilityLevel(iota) + PostVisibilityFriends + PostVisibilityFiltered + PostVisibilitySelected + PostVisibilityNone +) + type Post struct { BaseModel @@ -28,8 +38,13 @@ type Post struct { RepostTo *Post `json:"repost_to" gorm:"foreignKey:RepostID"` Realm *Realm `json:"realm"` + VisibleUsers datatypes.JSONSlice[uint] `json:"visible_users_list"` + InvisibleUsers datatypes.JSONSlice[uint] `json:"invisible_users_list"` + Visibility PostVisibilityLevel `json:"visibility"` + EditedAt *time.Time `json:"edited_at"` PinnedAt *time.Time `json:"pinned_at"` + LockedAt *time.Time `json:"locked_at"` IsDraft bool `json:"is_draft"` PublishedAt *time.Time `json:"published_at"` diff --git a/pkg/internal/server/api/articles_api.go b/pkg/internal/server/api/articles_api.go index ccb4904..cc236f1 100644 --- a/pkg/internal/server/api/articles_api.go +++ b/pkg/internal/server/api/articles_api.go @@ -29,6 +29,9 @@ func createArticle(c *fiber.Ctx) error { Categories []models.Category `json:"categories"` PublishedAt *time.Time `json:"published_at"` PublishedUntil *time.Time `json:"published_until"` + VisibleUsers []uint `json:"visible_users_list"` + InvisibleUsers []uint `json:"invisible_users_list"` + Visibility *int8 `json:"visibility"` IsDraft bool `json:"is_draft"` RealmAlias *string `json:"realm"` } @@ -57,9 +60,17 @@ func createArticle(c *fiber.Ctx) error { IsDraft: data.IsDraft, PublishedAt: data.PublishedAt, PublishedUntil: data.PublishedUntil, + VisibleUsers: data.VisibleUsers, + InvisibleUsers: data.InvisibleUsers, AuthorID: user.ID, } + if data.Visibility != nil { + item.Visibility = *data.Visibility + } else { + item.Visibility = models.PostVisibilityAll + } + if data.RealmAlias != nil { if realm, err := services.GetRealmWithAlias(*data.RealmAlias); err != nil { return fiber.NewError(fiber.StatusBadRequest, err.Error()) @@ -92,9 +103,12 @@ func editArticle(c *fiber.Ctx) error { Attachments []uint `json:"attachments"` Tags []models.Tag `json:"tags"` Categories []models.Category `json:"categories"` - IsDraft bool `json:"is_draft"` PublishedAt *time.Time `json:"published_at"` PublishedUntil *time.Time `json:"published_until"` + VisibleUsers []uint `json:"visible_users_list"` + InvisibleUsers []uint `json:"invisible_users_list"` + Visibility *int8 `json:"visibility"` + IsDraft bool `json:"is_draft"` } if err := exts.BindAndValidate(c, &data); err != nil { @@ -131,6 +145,12 @@ func editArticle(c *fiber.Ctx) error { item.Categories = data.Categories item.IsDraft = data.IsDraft item.PublishedUntil = data.PublishedUntil + item.VisibleUsers = data.VisibleUsers + item.InvisibleUsers = data.InvisibleUsers + + if data.Visibility != nil { + item.Visibility = *data.Visibility + } if item, err := services.EditPost(item); err != nil { return fiber.NewError(fiber.StatusBadRequest, err.Error()) diff --git a/pkg/internal/server/api/index.go b/pkg/internal/server/api/index.go index f2eeccd..500633d 100644 --- a/pkg/internal/server/api/index.go +++ b/pkg/internal/server/api/index.go @@ -13,7 +13,8 @@ func MapAPIs(app *fiber.App, baseURL string) { recommendations := api.Group("/recommendations").Name("Recommendations API") { - recommendations.Get("/", listRecommendationDefault) + recommendations.Get("/", listRecommendationNews) + recommendations.Get("/featured", listRecommendationFeatured) recommendations.Get("/shuffle", listRecommendationShuffle) } diff --git a/pkg/internal/server/api/recommendation_api.go b/pkg/internal/server/api/recommendation_api.go index c45fdcd..e7b1560 100644 --- a/pkg/internal/server/api/recommendation_api.go +++ b/pkg/internal/server/api/recommendation_api.go @@ -4,20 +4,62 @@ import ( "fmt" "git.solsynth.dev/hydrogen/interactive/pkg/internal/database" + "git.solsynth.dev/hydrogen/interactive/pkg/internal/models" "git.solsynth.dev/hydrogen/interactive/pkg/internal/services" "github.com/gofiber/fiber/v2" ) -func listRecommendationDefault(c *fiber.Ctx) error { +func listRecommendationFeatured(c *fiber.Ctx) error { take := c.QueryInt("take", 0) offset := c.QueryInt("offset", 0) realmId := c.QueryInt("realmId", 0) - maxDownVote := c.QueryInt("maxDownVote", 15) tx := services.FilterPostDraft(database.C) - if maxDownVote > 0 { - tx = tx.Joins("Author").Where("\"Author\".total_downvote - \"Author\".total_upvote <= ?", maxDownVote) + + if user, authenticated := c.Locals("user").(models.Account); authenticated { + tx = services.FilterPostWithUserContext(tx, &user) + } else { + tx = services.FilterPostWithUserContext(tx, nil) } + + 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.FilterPostWithRealm(tx, realm.ID) + } + } + + countTx := tx + count, err := services.CountPost(countTx) + if err != nil { + return fiber.NewError(fiber.StatusInternalServerError, err.Error()) + } + + items, err := services.ListPost(tx, take, offset, "published_at DESC") + if err != nil { + return fiber.NewError(fiber.StatusBadRequest, err.Error()) + } + + return c.JSON(fiber.Map{ + "count": count, + "data": items, + }) +} + +func listRecommendationNews(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 user, authenticated := c.Locals("user").(models.Account); authenticated { + tx = services.FilterPostWithUserContext(tx, &user) + } else { + tx = services.FilterPostWithUserContext(tx, nil) + } + 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)) @@ -47,13 +89,15 @@ func listRecommendationShuffle(c *fiber.Ctx) error { take := c.QueryInt("take", 0) offset := c.QueryInt("offset", 0) realmId := c.QueryInt("realmId", 0) - maxDownVote := c.QueryInt("maxDownVote", 15) tx := services.FilterPostDraft(database.C) - if maxDownVote > 0 { - tx = tx.Joins("Author").Where("\"Author\".total_downvote - \"Author\".total_upvote <= ?", maxDownVote) + + if user, authenticated := c.Locals("user").(models.Account); authenticated { + tx = services.FilterPostWithUserContext(tx, &user) + } else { + tx = services.FilterPostWithUserContext(tx, nil) } - tx = services.FilterPostDraft(tx) + 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)) diff --git a/pkg/internal/server/api/stories_api.go b/pkg/internal/server/api/stories_api.go index a05e758..9ab4c2b 100644 --- a/pkg/internal/server/api/stories_api.go +++ b/pkg/internal/server/api/stories_api.go @@ -29,6 +29,9 @@ func createStory(c *fiber.Ctx) error { Categories []models.Category `json:"categories"` PublishedAt *time.Time `json:"published_at"` PublishedUntil *time.Time `json:"published_until"` + VisibleUsers []uint `json:"visible_users_list"` + InvisibleUsers []uint `json:"invisible_users_list"` + Visibility *int8 `json:"visibility"` IsDraft bool `json:"is_draft"` RealmAlias *string `json:"realm"` ReplyTo *uint `json:"reply_to"` @@ -59,9 +62,17 @@ func createStory(c *fiber.Ctx) error { PublishedAt: data.PublishedAt, PublishedUntil: data.PublishedUntil, IsDraft: data.IsDraft, + VisibleUsers: data.VisibleUsers, + InvisibleUsers: data.InvisibleUsers, AuthorID: user.ID, } + if data.Visibility != nil { + item.Visibility = *data.Visibility + } else { + item.Visibility = models.PostVisibilityAll + } + if data.ReplyTo != nil { var replyTo models.Post if err := database.C.Where("id = ?", data.ReplyTo).First(&replyTo).Error; err != nil { @@ -113,6 +124,9 @@ func editStory(c *fiber.Ctx) error { Categories []models.Category `json:"categories"` PublishedAt *time.Time `json:"published_at"` PublishedUntil *time.Time `json:"published_until"` + VisibleUsers []uint `json:"visible_users_list"` + InvisibleUsers []uint `json:"invisible_users_list"` + Visibility *int8 `json:"visibility"` IsDraft bool `json:"is_draft"` } @@ -151,6 +165,12 @@ func editStory(c *fiber.Ctx) error { item.Categories = data.Categories item.PublishedUntil = data.PublishedUntil item.IsDraft = data.IsDraft + item.VisibleUsers = data.VisibleUsers + item.InvisibleUsers = data.InvisibleUsers + + if data.Visibility != nil { + item.Visibility = *data.Visibility + } if item, err := services.EditPost(item); err != nil { return fiber.NewError(fiber.StatusBadRequest, err.Error()) diff --git a/pkg/internal/services/posts.go b/pkg/internal/services/posts.go index 6381440..5bc10c7 100644 --- a/pkg/internal/services/posts.go +++ b/pkg/internal/services/posts.go @@ -13,6 +13,19 @@ import ( "gorm.io/gorm" ) +func FilterPostWithUserContext(tx *gorm.DB, user *models.Account) *gorm.DB { + if user == nil { + return tx.Where("visibility = ?", models.PostVisibilityAll) + } + + tx = tx.Where("visibility != ?", models.PostVisibilityFriends) // TODO Blocked by dealer, need support get friend list + tx = tx.Where("visibility = ? AND ? = ANY (visible_users_list::jsonb[])", models.PostVisibilitySelected, user.ID) + tx = tx.Where("visibility = ? AND NOT ( ? = ANY (invisible_users_list::jsonb[]) )", models.PostVisibilitySelected, user.ID) + tx = tx.Where("visibility != ?", models.PostVisibilityNone) + + return tx +} + func FilterPostWithCategory(tx *gorm.DB, alias string) *gorm.DB { prefix := viper.GetString("database.prefix") return tx.Joins(fmt.Sprintf("JOIN %spost_categories ON %sposts.id = %spost_categories.post_id", prefix, prefix, prefix)). @@ -210,10 +223,6 @@ func EnsurePostCategoriesAndTags(item models.Post) (models.Post, error) { } func NewPost(user models.Account, item models.Post) (models.Post, error) { - if !item.IsDraft && item.PublishedAt == nil { - item.PublishedAt = lo.ToPtr(time.Now()) - } - item, err := EnsurePostCategoriesAndTags(item) if err != nil { return item, err @@ -226,7 +235,9 @@ func NewPost(user models.Account, item models.Post) (models.Post, error) { } } - item.EditedAt = lo.ToPtr(time.Now()) + if !item.IsDraft && item.PublishedAt == nil { + item.PublishedAt = lo.ToPtr(time.Now()) + } if err := database.C.Save(&item).Error; err != nil { return item, err @@ -243,7 +254,7 @@ func NewPost(user models.Account, item models.Post) (models.Post, error) { err = NotifyPosterAccount( op.Author, "Post got replied", - fmt.Sprintf("%s (%s) replied your post.", user.Nick, user.Name), + fmt.Sprintf("%s (%s) replied your post (#%d).", user.Nick, user.Name, op.ID), lo.ToPtr(fmt.Sprintf("%s replied you", user.Nick)), ) if err != nil { @@ -257,6 +268,7 @@ func NewPost(user models.Account, item models.Post) (models.Post, error) { } func EditPost(item models.Post) (models.Post, error) { + item.EditedAt = lo.ToPtr(time.Now()) item, err := EnsurePostCategoriesAndTags(item) if err != nil { return item, err