Skip to content

Commit

Permalink
feat: add /v2/news/post_reviews endpoint
Browse files Browse the repository at this point in the history
  • Loading branch information
Aylie Chou committed Apr 17, 2024
1 parent 41ad8c5 commit c00d5bc
Show file tree
Hide file tree
Showing 8 changed files with 161 additions and 1 deletion.
3 changes: 3 additions & 0 deletions configs/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,7 @@ news:
topic_page_timeout: 5s
index_page_timeout: 5s
author_page_timeout: 5s
review_page_timeout: 5s
neticrm:
project_id: "" # gcp project id
pub_topic: "" # pub/sub topic
Expand Down Expand Up @@ -202,6 +203,7 @@ type NewsConfig struct {
TopicPageTimeout time.Duration `yaml:"topic_page_timeout"`
IndexPageTimeout time.Duration `yaml:"index_page_timeout"`
AuthorPageTimeout time.Duration `yaml:"author_page_timeout"`
ReviewPageTimeout time.Duration `yaml:"review_page_timeout"`
}

type NeticrmPubConfig struct {
Expand Down Expand Up @@ -299,6 +301,7 @@ func buildConf() ConfYaml {
conf.News.TopicPageTimeout = viper.GetDuration("news.topic_page_timeout")
conf.News.IndexPageTimeout = viper.GetDuration("news.index_page_timeout")
conf.News.AuthorPageTimeout = viper.GetDuration("news.author_page_timeout")
conf.News.ReviewPageTimeout = viper.GetDuration("news.review_page_timeout")

// Neticrm
conf.Neticrm.ProjectID = viper.GetString("neticrm.project_id")
Expand Down
22 changes: 22 additions & 0 deletions controllers/news_v2.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ type newsV2Storage interface {
GetFullTopics(context.Context, *news.Query) ([]news.Topic, error)
GetMetaOfTopics(context.Context, *news.Query) ([]news.MetaOfTopic, error)
GetAuthors(context.Context, *news.Query) ([]news.Author, error)
GetPostReviewData(context.Context, *news.Query) ([]news.Review, error)

GetTags(context.Context, *news.Query) ([]news.Tag, error)

Expand Down Expand Up @@ -534,3 +535,24 @@ func (nc *newsV2Controller) GetPostsByAuthor(c *gin.Context) {
"limit": q.Limit,
}}})
}

func (nc *newsV2Controller) GetPostReviews(c *gin.Context) {
var err error

ctx, cancel := context.WithTimeout(c, globals.Conf.News.ReviewPageTimeout)
defer cancel()

defer func() {
if err != nil {
nc.helperCleanup(c, err)
}
}()

q := news.NewQuery(news.WithFilterNull(), news.WithSortOrder(true), news.WithLimit(8))
reviews, err := nc.Storage.GetPostReviewData(ctx, q)
if err != nil {
return
}

c.JSON(http.StatusOK, gin.H{"status": "success", "data": reviews})
}
43 changes: 43 additions & 0 deletions internal/mongo/document.go
Original file line number Diff line number Diff line change
Expand Up @@ -110,3 +110,46 @@ func BuildCategorySetStage() []bson.D {

return result
}

func BuildReviewLookupStatements() []bson.D {
var stages []bson.D

// lookup posts
stages = append(stages, bson.D{{
Key: StageLookup, Value: bson.D{
{Key: MetaFrom, Value: "posts"},
{Key: MetaLocalField, Value: "post_id"},
{Key: MetaForeignField, Value: "_id"},
{Key: MetaAs, Value: "post"},
},
}})

// lookup images
stages = append(stages, bson.D{{
Key: StageLookup, Value: bson.D{
{Key: MetaFrom, Value: "images"},
{Key: MetaLocalField, Value: "post.og_image"},
{Key: MetaForeignField, Value: "_id"},
{Key: MetaAs, Value: "og_image"},
},
}})

// unwind images
stages = append(stages, BuildUnwindStage("og_image"))
stages = append(stages, BuildUnwindStage("post"))

// project fields
stages = append(stages, bson.D{{
Key: StageProject, Value: bson.D{
{Key: "order", Value: 1},
{Key: "og_image", Value: 1},
{Key: "post_id", Value: "$post._id"},
{Key: "slug", Value: "$post.slug"},
{Key: "title", Value: "$post.title"},
{Key: "og_description", Value: "$post.og_description"},
{Key: "reviewWord", Value: "$post.reviewWord"},
},
}})

return stages
}
10 changes: 10 additions & 0 deletions internal/news/model.go
Original file line number Diff line number Diff line change
Expand Up @@ -179,3 +179,13 @@ type MetaOfFootprint struct {
BookmarkID string `json:"bookmark_id"`
UpdatedAt time.Time `json:"footprint_updated_at"`
}

type Review struct {
Order int `bson:"order" json:"order"`
PostID primitive.ObjectID `bson:"post_id" json:"post_id"`
Slug string `bson:"slug" json:"slug"`
Title string `bson:"title" json:"title"`
OgDescription string `bson:"og_description" json:"og_description"`
OgImage *Image `bson:"og_image" json:"og_image,omitempty"`
ReviewWord string `bson:"reviewWord" json:"reviewWord"`
}
12 changes: 11 additions & 1 deletion internal/news/mongo.go
Original file line number Diff line number Diff line change
Expand Up @@ -250,6 +250,7 @@ func hexToObjectIDs(hs []string) []primitive.ObjectID {
type mongoSort struct {
PublishedDate query.Order `mongo:"publishedDate"`
UpdatedAt query.Order `mongo:"updatedAt"`
Order query.Order `mongo:"order"`
}

func (ms mongoSort) BuildStage() []bson.D {
Expand All @@ -266,7 +267,7 @@ func (ms mongoSort) BuildStage() []bson.D {
switch fieldV.Interface().(type) {
case query.Order:
v := fieldV.Interface().(query.Order)
if !v.IsAsc.IsZero() {
if v.IsAsc.Valid {
if v.IsAsc.Bool {
sortBy = append(sortBy, mongo.BuildElement(tag, mongo.OrderAsc))
} else {
Expand All @@ -288,6 +289,7 @@ func fromSort(s SortBy) mongoSort {
return mongoSort{
PublishedDate: s.PublishedDate,
UpdatedAt: s.UpdatedAt,
Order: s.Order,
}
}

Expand All @@ -300,6 +302,7 @@ const (
ColTags = "tags"
ColPosts = "posts"
ColTopics = "topics"
ColReviews = "reviews"

// TODO: rename fields to writer
fieldWriters = "writters"
Expand All @@ -323,6 +326,7 @@ const (
fieldState = "state"
fieldThumbnail = "image"
fieldBio = "bio"
fieldPost = "post"
)

type lookupInfo struct {
Expand Down Expand Up @@ -377,6 +381,10 @@ var (
fieldHeroImage: {Collection: ColImages, ToUnwind: true},
fieldCategorySet: {},
}

LookupReview = map[string]lookupInfo{
fieldPost: {},
}
)

func BuildLookupStatements(m map[string]lookupInfo) []bson.D {
Expand All @@ -385,6 +393,8 @@ func BuildLookupStatements(m map[string]lookupInfo) []bson.D {
if field == fieldCategorySet {
// join category_set data
stages = append(stages, mongo.BuildCategorySetStage()...)
} else if field == fieldPost {
stages = append(stages, mongo.BuildReviewLookupStatements()...)
} else {
if shouldPreserveOrder(field) {
stages = append(stages, buildPreserveLookupOrderStatement(field, info)...)
Expand Down
15 changes: 15 additions & 0 deletions internal/news/query.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ type Filter struct {
type SortBy struct {
PublishedDate query.Order
UpdatedAt query.Order
Order query.Order
}

const (
Expand Down Expand Up @@ -134,13 +135,27 @@ func WithFilterIDs(ids ...string) Option {
}
}

// WithFilterNull reset filter on the query
func WithFilterNull() Option {
return func(q *Query) {
q.Filter = Filter{}
}
}

// SortUpdatedAt updates the query to sort by updatedAt field
func WithSortUpdatedAt(isAsc bool) Option {
return func(q *Query) {
q.Sort = SortBy{UpdatedAt: query.Order{IsAsc: null.BoolFrom(isAsc)}}
}
}

// WithSortOrder updates the query to sort by order field
func WithSortOrder(isAsc bool) Option {
return func(q *Query) {
q.Sort = SortBy{Order: query.Order{IsAsc: null.NewBool(isAsc, true)}}
}
}

func ParseSinglePostQuery(c *gin.Context) *Query {
return parseSingleQuery(c)
}
Expand Down
1 change: 1 addition & 0 deletions routers/router.go
Original file line number Diff line number Diff line change
Expand Up @@ -153,6 +153,7 @@ func SetupRouter(cf *controllers.ControllerFactory) (engine *gin.Engine) {
ncV2 := cf.GetNewsV2Controller()
v2Group.GET("/posts", middlewares.PassAuthUserID(), middlewares.SetCacheControl("public,max-age=900"), ncV2.GetPosts)
v2Group.GET("/posts/:slug", middlewares.SetCacheControl("public,max-age=900"), ncV2.GetAPost)
v2Group.GET("/post_reviews", middlewares.ValidateAuthentication(), middlewares.ValidateAuthorization(), middlewares.SetCacheControl("no-cache"), ncV2.GetPostReviews)

v2Group.GET("/tags", middlewares.SetCacheControl("public,max-age=900"), ncV2.GetTags)

Expand Down
56 changes: 56 additions & 0 deletions storage/news_v2.go
Original file line number Diff line number Diff line change
Expand Up @@ -533,3 +533,59 @@ func (m *mongoStorage) getTags(ctx context.Context, stages []bson.D) <-chan fetc
}(ctx, stages)
return result
}

func (m *mongoStorage) GetPostReviewData(ctx context.Context, q *news.Query) ([]news.Review, error) {
var reviews []news.Review

mq := news.NewMongoQuery(q)

// build aggregate stages from query
stages := news.BuildQueryStatements(mq)
// build lookup(join) stages according to required fields
stages = append(stages, news.BuildLookupStatements(news.LookupReview)...)

select {
case <-ctx.Done():
return nil, errors.WithStack(ctx.Err())
case result, ok := <-m.getReviews(ctx, stages):
switch {
case !ok:
return nil, errors.WithStack(ctx.Err())
case result.Error != nil:
return nil, result.Error
}
reviews = result.Content.([]news.Review)
}

return reviews, nil
}

func (m *mongoStorage) getReviews(ctx context.Context, stages []bson.D) <-chan fetchResult {
result := make(chan fetchResult)
go func(ctx context.Context, stages []bson.D) {
defer close(result)
cursor, err := m.Database(globals.Conf.DB.Mongo.DBname).Collection(news.ColReviews).Aggregate(ctx, stages)
if err != nil {
result <- fetchResult{Error: errors.WithStack(err)}
return
}
defer cursor.Close(ctx)

var reviews []news.Review
for cursor.Next(ctx) {
var review news.Review
err := cursor.Decode(&review)
if err != nil {
result <- fetchResult{Error: errors.WithStack(err)}
return
}
reviews = append(reviews, review)
}
if err := cursor.Err(); err != nil {
result <- fetchResult{Error: errors.WithStack(err)}
return
}
result <- fetchResult{Content: reviews}
}(ctx, stages)
return result
}

0 comments on commit c00d5bc

Please sign in to comment.