✨ Post visibility
This commit is contained in:
		@@ -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"`
 | 
			
		||||
 
 | 
			
		||||
@@ -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())
 | 
			
		||||
 
 | 
			
		||||
@@ -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)
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -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))
 | 
			
		||||
 
 | 
			
		||||
@@ -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())
 | 
			
		||||
 
 | 
			
		||||
@@ -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
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user