2021-04-25 19:33:22 +00:00
|
|
|
package website
|
|
|
|
|
|
|
|
import (
|
2021-05-30 18:35:01 +00:00
|
|
|
"fmt"
|
2021-04-25 19:33:22 +00:00
|
|
|
"math"
|
|
|
|
"net/http"
|
|
|
|
"strconv"
|
2021-05-30 18:35:01 +00:00
|
|
|
"strings"
|
2021-04-25 19:33:22 +00:00
|
|
|
"time"
|
|
|
|
|
2021-05-30 18:35:01 +00:00
|
|
|
"github.com/google/uuid"
|
|
|
|
|
2021-04-25 19:33:22 +00:00
|
|
|
"git.handmade.network/hmn/hmn/src/db"
|
2021-04-28 03:29:13 +00:00
|
|
|
"git.handmade.network/hmn/hmn/src/hmnurl"
|
2021-04-25 19:33:22 +00:00
|
|
|
"git.handmade.network/hmn/hmn/src/models"
|
|
|
|
"git.handmade.network/hmn/hmn/src/oops"
|
|
|
|
"git.handmade.network/hmn/hmn/src/templates"
|
2021-05-05 20:34:32 +00:00
|
|
|
"git.handmade.network/hmn/hmn/src/utils"
|
2021-04-25 19:33:22 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
type FeedData struct {
|
|
|
|
templates.BaseData
|
|
|
|
|
2021-05-11 22:53:23 +00:00
|
|
|
Posts []templates.PostListItem
|
|
|
|
Pagination templates.Pagination
|
|
|
|
AtomFeedUrl string
|
|
|
|
MarkAllReadUrl string
|
2021-04-25 19:33:22 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func Feed(c *RequestContext) ResponseData {
|
|
|
|
const postsPerPage = 30
|
|
|
|
|
2021-04-26 06:56:49 +00:00
|
|
|
c.Perf.StartBlock("SQL", "Count posts")
|
2021-04-25 19:33:22 +00:00
|
|
|
numPosts, err := db.QueryInt(c.Context(), c.Conn,
|
|
|
|
`
|
|
|
|
SELECT COUNT(*)
|
|
|
|
FROM
|
|
|
|
handmade_post AS post
|
|
|
|
WHERE
|
2021-04-28 03:29:13 +00:00
|
|
|
post.category_kind = ANY ($1)
|
2021-05-05 20:34:32 +00:00
|
|
|
AND deleted = FALSE
|
|
|
|
AND post.thread_id IS NOT NULL
|
2021-04-25 19:33:22 +00:00
|
|
|
`,
|
2021-04-28 03:29:13 +00:00
|
|
|
[]models.CategoryKind{models.CatKindForum, models.CatKindBlog, models.CatKindWiki, models.CatKindLibraryResource},
|
|
|
|
)
|
2021-04-26 06:56:49 +00:00
|
|
|
c.Perf.EndBlock()
|
2021-04-25 19:33:22 +00:00
|
|
|
if err != nil {
|
|
|
|
return ErrorResponse(http.StatusInternalServerError, oops.New(err, "failed to get count of feed posts"))
|
|
|
|
}
|
|
|
|
|
2021-04-29 04:52:27 +00:00
|
|
|
numPages := int(math.Ceil(float64(numPosts) / postsPerPage))
|
2021-04-25 19:33:22 +00:00
|
|
|
|
|
|
|
page := 1
|
2021-04-29 03:07:14 +00:00
|
|
|
pageString, hasPage := c.PathParams["page"]
|
|
|
|
if hasPage && pageString != "" {
|
2021-04-25 19:33:22 +00:00
|
|
|
if pageParsed, err := strconv.Atoi(pageString); err == nil {
|
|
|
|
page = pageParsed
|
|
|
|
} else {
|
2021-05-05 20:34:32 +00:00
|
|
|
return c.Redirect(hmnurl.BuildFeed(), http.StatusSeeOther)
|
2021-04-25 19:33:22 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
if page < 1 || numPages < page {
|
2021-05-05 20:34:32 +00:00
|
|
|
return c.Redirect(hmnurl.BuildFeedWithPage(utils.IntClamp(1, page, numPages)), http.StatusSeeOther)
|
2021-04-25 19:33:22 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
howManyPostsToSkip := (page - 1) * postsPerPage
|
|
|
|
|
|
|
|
pagination := templates.Pagination{
|
|
|
|
Current: page,
|
|
|
|
Total: numPages,
|
|
|
|
|
2021-05-05 20:34:32 +00:00
|
|
|
FirstUrl: hmnurl.BuildFeed(),
|
|
|
|
LastUrl: hmnurl.BuildFeedWithPage(numPages),
|
|
|
|
NextUrl: hmnurl.BuildFeedWithPage(utils.IntClamp(1, page+1, numPages)),
|
|
|
|
PreviousUrl: hmnurl.BuildFeedWithPage(utils.IntClamp(1, page-1, numPages)),
|
2021-04-25 19:33:22 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
var currentUserId *int
|
|
|
|
if c.CurrentUser != nil {
|
|
|
|
currentUserId = &c.CurrentUser.ID
|
|
|
|
}
|
|
|
|
|
2021-05-30 18:35:01 +00:00
|
|
|
c.Perf.StartBlock("SQL", "Fetch category tree")
|
|
|
|
categoryTree := models.GetFullCategoryTree(c.Context(), c.Conn)
|
|
|
|
lineageBuilder := models.MakeCategoryLineageBuilder(categoryTree)
|
|
|
|
c.Perf.EndBlock()
|
|
|
|
|
|
|
|
posts, err := fetchAllPosts(c, lineageBuilder, currentUserId, howManyPostsToSkip, postsPerPage)
|
|
|
|
if err != nil {
|
|
|
|
return ErrorResponse(http.StatusInternalServerError, oops.New(err, "failed to fetch feed posts"))
|
|
|
|
}
|
|
|
|
|
|
|
|
baseData := getBaseData(c)
|
|
|
|
baseData.BodyClasses = append(baseData.BodyClasses, "feed")
|
|
|
|
|
|
|
|
var res ResponseData
|
2021-06-22 10:12:17 +00:00
|
|
|
err = res.WriteTemplate("feed.html", FeedData{
|
2021-05-30 18:35:01 +00:00
|
|
|
BaseData: baseData,
|
|
|
|
|
|
|
|
AtomFeedUrl: hmnurl.BuildAtomFeed(),
|
|
|
|
MarkAllReadUrl: hmnurl.BuildMarkRead(0),
|
|
|
|
Posts: posts,
|
|
|
|
Pagination: pagination,
|
|
|
|
}, c.Perf)
|
2021-06-22 10:12:17 +00:00
|
|
|
if err != nil {
|
|
|
|
panic(err)
|
|
|
|
}
|
2021-05-30 18:35:01 +00:00
|
|
|
|
|
|
|
return res
|
|
|
|
}
|
|
|
|
|
|
|
|
type FeedType int
|
|
|
|
|
|
|
|
const (
|
|
|
|
FeedTypeAll = iota
|
|
|
|
FeedTypeProjects
|
|
|
|
FeedTypeShowcase
|
|
|
|
)
|
|
|
|
|
|
|
|
// NOTE(asaf): UUID values copied from old website
|
|
|
|
var (
|
|
|
|
FeedIDAll = "urn:uuid:1084fd28-993a-4961-9011-39ddeaeb3711"
|
|
|
|
FeedIDProjects = "urn:uuid:cfad0d50-cbcf-11e7-82d7-db1d52543cc7"
|
|
|
|
FeedIDShowcase = "urn:uuid:37d29027-2892-5a21-b521-951246c7aa46"
|
|
|
|
)
|
|
|
|
|
|
|
|
type AtomFeedData struct {
|
|
|
|
Title string
|
|
|
|
Subtitle string
|
|
|
|
|
|
|
|
HomepageUrl string
|
|
|
|
AtomFeedUrl string
|
|
|
|
FeedUrl string
|
|
|
|
|
|
|
|
CopyrightStatement string
|
|
|
|
SiteVersion string
|
|
|
|
Updated time.Time
|
|
|
|
FeedID string
|
|
|
|
|
|
|
|
FeedType FeedType
|
|
|
|
Posts []templates.PostListItem
|
2021-05-31 23:23:04 +00:00
|
|
|
Projects []templates.Project
|
2021-06-22 12:02:47 +00:00
|
|
|
Snippets []templates.TimelineItem
|
2021-05-30 18:35:01 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func AtomFeed(c *RequestContext) ResponseData {
|
|
|
|
itemsPerFeed := 25 // NOTE(asaf): Copied from old website
|
|
|
|
|
|
|
|
feedData := AtomFeedData{
|
|
|
|
HomepageUrl: hmnurl.BuildHomepage(),
|
|
|
|
|
|
|
|
CopyrightStatement: fmt.Sprintf("Copyright (C) 2014-%d Handmade.Network and its contributors", time.Now().Year()),
|
|
|
|
SiteVersion: "2.0",
|
|
|
|
}
|
|
|
|
|
|
|
|
feedType, hasType := c.PathParams["feedtype"]
|
|
|
|
if !hasType || len(feedType) == 0 {
|
|
|
|
feedData.Title = "New Threads, Blog Posts, Replies and Comments | Site-wide | Handmade.Network"
|
|
|
|
feedData.Subtitle = feedData.Title
|
|
|
|
feedData.FeedType = FeedTypeAll
|
|
|
|
feedData.FeedID = FeedIDAll
|
|
|
|
feedData.AtomFeedUrl = hmnurl.BuildAtomFeed()
|
|
|
|
feedData.FeedUrl = hmnurl.BuildFeed()
|
|
|
|
|
|
|
|
c.Perf.StartBlock("SQL", "Fetch category tree")
|
|
|
|
categoryTree := models.GetFullCategoryTree(c.Context(), c.Conn)
|
|
|
|
lineageBuilder := models.MakeCategoryLineageBuilder(categoryTree)
|
|
|
|
c.Perf.EndBlock()
|
|
|
|
|
|
|
|
posts, err := fetchAllPosts(c, lineageBuilder, nil, 0, itemsPerFeed)
|
|
|
|
if err != nil {
|
|
|
|
return ErrorResponse(http.StatusInternalServerError, oops.New(err, "failed to fetch feed posts"))
|
|
|
|
}
|
|
|
|
feedData.Posts = posts
|
|
|
|
|
|
|
|
updated := time.Now()
|
|
|
|
if len(posts) > 0 {
|
|
|
|
updated = posts[0].Date
|
|
|
|
}
|
|
|
|
feedData.Updated = updated
|
|
|
|
} else {
|
|
|
|
switch strings.ToLower(feedType) {
|
|
|
|
case "projects":
|
2021-05-31 23:23:04 +00:00
|
|
|
feedData.Title = "New Projects | Site-wide | Handmade.Network"
|
|
|
|
feedData.Subtitle = feedData.Title
|
|
|
|
feedData.FeedType = FeedTypeProjects
|
|
|
|
feedData.FeedID = FeedIDProjects
|
|
|
|
feedData.AtomFeedUrl = hmnurl.BuildAtomFeedForProjects()
|
2021-06-06 23:48:43 +00:00
|
|
|
feedData.FeedUrl = hmnurl.BuildProjectIndex(1)
|
2021-05-31 23:23:04 +00:00
|
|
|
|
|
|
|
c.Perf.StartBlock("SQL", "Fetching projects")
|
|
|
|
type projectResult struct {
|
|
|
|
Project models.Project `db:"project"`
|
|
|
|
}
|
|
|
|
projects, err := db.Query(c.Context(), c.Conn, projectResult{},
|
|
|
|
`
|
|
|
|
SELECT $columns
|
2021-06-06 23:48:43 +00:00
|
|
|
FROM
|
|
|
|
handmade_project AS project
|
|
|
|
WHERE
|
|
|
|
project.lifecycle = ANY($1)
|
|
|
|
AND project.flags = 0
|
2021-05-31 23:23:04 +00:00
|
|
|
ORDER BY date_approved DESC
|
2021-06-06 23:48:43 +00:00
|
|
|
LIMIT $2
|
2021-05-31 23:23:04 +00:00
|
|
|
`,
|
2021-06-06 23:48:43 +00:00
|
|
|
models.VisibleProjectLifecycles,
|
2021-05-31 23:23:04 +00:00
|
|
|
itemsPerFeed,
|
|
|
|
)
|
|
|
|
if err != nil {
|
|
|
|
return ErrorResponse(http.StatusInternalServerError, oops.New(err, "failed to fetch feed projects"))
|
|
|
|
}
|
|
|
|
var projectIds []int
|
|
|
|
projectMap := make(map[int]*templates.Project)
|
|
|
|
for _, p := range projects.ToSlice() {
|
|
|
|
project := p.(*projectResult).Project
|
2021-06-06 23:48:43 +00:00
|
|
|
templateProject := templates.ProjectToTemplate(&project, c.Theme)
|
2021-05-31 23:23:04 +00:00
|
|
|
templateProject.UUID = uuid.NewSHA1(uuid.NameSpaceURL, []byte(templateProject.Url)).URN()
|
|
|
|
|
|
|
|
projectIds = append(projectIds, project.ID)
|
|
|
|
projectMap[project.ID] = &templateProject
|
|
|
|
feedData.Projects = append(feedData.Projects, templateProject)
|
|
|
|
}
|
|
|
|
c.Perf.EndBlock()
|
|
|
|
|
|
|
|
c.Perf.StartBlock("SQL", "Fetching project owners")
|
|
|
|
type ownerResult struct {
|
|
|
|
User models.User `db:"auth_user"`
|
|
|
|
ProjectID int `db:"project_groups.project_id"`
|
|
|
|
}
|
|
|
|
owners, err := db.Query(c.Context(), c.Conn, ownerResult{},
|
|
|
|
`
|
|
|
|
SELECT $columns
|
|
|
|
FROM
|
|
|
|
auth_user
|
|
|
|
INNER JOIN auth_user_groups AS user_groups ON auth_user.id = user_groups.user_id
|
|
|
|
INNER JOIN handmade_project_groups AS project_groups ON user_groups.group_id = project_groups.group_id
|
|
|
|
WHERE
|
|
|
|
project_groups.project_id = ANY($1)
|
|
|
|
`,
|
|
|
|
projectIds,
|
|
|
|
)
|
|
|
|
if err != nil {
|
|
|
|
return ErrorResponse(http.StatusInternalServerError, oops.New(err, "failed to fetch feed projects owners"))
|
|
|
|
}
|
|
|
|
for _, res := range owners.ToSlice() {
|
|
|
|
owner := res.(*ownerResult)
|
|
|
|
templateProject := projectMap[owner.ProjectID]
|
|
|
|
templateProject.Owners = append(templateProject.Owners, templates.UserToTemplate(&owner.User, ""))
|
|
|
|
}
|
|
|
|
c.Perf.EndBlock()
|
2021-06-22 12:02:47 +00:00
|
|
|
updated := time.Now()
|
|
|
|
if len(feedData.Projects) > 0 {
|
|
|
|
updated = feedData.Projects[0].DateApproved
|
|
|
|
}
|
|
|
|
feedData.Updated = updated
|
2021-05-30 18:35:01 +00:00
|
|
|
case "showcase":
|
2021-06-22 12:02:47 +00:00
|
|
|
feedData.Title = "Showcase | Site-wide | Handmade.Network"
|
|
|
|
feedData.Subtitle = feedData.Title
|
|
|
|
feedData.FeedType = FeedTypeShowcase
|
|
|
|
feedData.FeedID = FeedIDShowcase
|
|
|
|
feedData.AtomFeedUrl = hmnurl.BuildAtomFeedForShowcase()
|
|
|
|
feedData.FeedUrl = hmnurl.BuildShowcase()
|
|
|
|
|
|
|
|
c.Perf.StartBlock("SQL", "Fetch showcase snippets")
|
|
|
|
type snippetQuery struct {
|
|
|
|
Owner models.User `db:"owner"`
|
|
|
|
Snippet models.Snippet `db:"snippet"`
|
|
|
|
Asset *models.Asset `db:"asset"`
|
|
|
|
DiscordMessage *models.DiscordMessage `db:"discord_message"`
|
|
|
|
}
|
|
|
|
snippetQueryResult, err := db.Query(c.Context(), c.Conn, snippetQuery{},
|
|
|
|
`
|
|
|
|
SELECT $columns
|
|
|
|
FROM
|
|
|
|
handmade_snippet AS snippet
|
|
|
|
INNER JOIN auth_user AS owner ON owner.id = snippet.owner_id
|
|
|
|
LEFT JOIN handmade_asset AS asset ON asset.id = snippet.asset_id
|
|
|
|
LEFT JOIN handmade_discordmessage AS discord_message ON discord_message.id = snippet.discord_message_id
|
|
|
|
ORDER BY snippet.when DESC
|
|
|
|
LIMIT $1
|
|
|
|
`,
|
|
|
|
itemsPerFeed,
|
|
|
|
)
|
|
|
|
if err != nil {
|
|
|
|
return ErrorResponse(http.StatusInternalServerError, oops.New(err, "failed to fetch snippets"))
|
|
|
|
}
|
|
|
|
snippetQuerySlice := snippetQueryResult.ToSlice()
|
|
|
|
for _, s := range snippetQuerySlice {
|
|
|
|
row := s.(*snippetQuery)
|
|
|
|
timelineItem := SnippetToTimelineItem(&row.Snippet, row.Asset, row.DiscordMessage, &row.Owner, c.Theme)
|
|
|
|
timelineItem.UUID = uuid.NewSHA1(uuid.NameSpaceURL, []byte(timelineItem.Url)).URN()
|
|
|
|
feedData.Snippets = append(feedData.Snippets, timelineItem)
|
|
|
|
}
|
|
|
|
c.Perf.EndBlock()
|
|
|
|
updated := time.Now()
|
|
|
|
if len(feedData.Snippets) > 0 {
|
|
|
|
updated = feedData.Snippets[0].Date
|
|
|
|
}
|
|
|
|
feedData.Updated = updated
|
2021-05-30 18:35:01 +00:00
|
|
|
default:
|
|
|
|
return FourOhFour(c)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
var res ResponseData
|
2021-06-22 10:12:17 +00:00
|
|
|
err := res.WriteTemplate("atom.xml", feedData, c.Perf)
|
|
|
|
if err != nil {
|
|
|
|
panic(err)
|
|
|
|
}
|
2021-05-30 18:35:01 +00:00
|
|
|
|
|
|
|
return res
|
|
|
|
}
|
|
|
|
|
|
|
|
func fetchAllPosts(c *RequestContext, lineageBuilder *models.CategoryLineageBuilder, currentUserID *int, offset int, limit int) ([]templates.PostListItem, error) {
|
2021-04-26 06:56:49 +00:00
|
|
|
c.Perf.StartBlock("SQL", "Fetch posts")
|
2021-04-25 19:33:22 +00:00
|
|
|
type feedPostQuery struct {
|
2021-05-11 22:53:23 +00:00
|
|
|
Post models.Post `db:"post"`
|
2021-05-30 18:35:01 +00:00
|
|
|
PostVersion models.PostVersion `db:"version"`
|
2021-05-11 22:53:23 +00:00
|
|
|
Thread models.Thread `db:"thread"`
|
|
|
|
Cat models.Category `db:"cat"`
|
|
|
|
Proj models.Project `db:"proj"`
|
|
|
|
LibraryResource *models.LibraryResource `db:"lib_resource"`
|
|
|
|
User models.User `db:"auth_user"`
|
|
|
|
ThreadLastReadTime *time.Time `db:"tlri.lastread"`
|
|
|
|
CatLastReadTime *time.Time `db:"clri.lastread"`
|
2021-04-25 19:33:22 +00:00
|
|
|
}
|
|
|
|
posts, err := db.Query(c.Context(), c.Conn, feedPostQuery{},
|
|
|
|
`
|
|
|
|
SELECT $columns
|
|
|
|
FROM
|
|
|
|
handmade_post AS post
|
2021-05-30 18:35:01 +00:00
|
|
|
JOIN handmade_postversion AS version ON version.id = post.current_id
|
2021-04-25 19:33:22 +00:00
|
|
|
JOIN handmade_thread AS thread ON thread.id = post.thread_id
|
2021-04-28 03:29:13 +00:00
|
|
|
JOIN handmade_category AS cat ON cat.id = post.category_id
|
2021-04-27 01:49:46 +00:00
|
|
|
JOIN handmade_project AS proj ON proj.id = post.project_id
|
2021-05-11 22:53:23 +00:00
|
|
|
LEFT JOIN handmade_threadlastreadinfo AS tlri ON (
|
2021-04-27 01:49:46 +00:00
|
|
|
tlri.thread_id = post.thread_id
|
2021-04-25 19:33:22 +00:00
|
|
|
AND tlri.user_id = $1
|
|
|
|
)
|
2021-05-11 22:53:23 +00:00
|
|
|
LEFT JOIN handmade_categorylastreadinfo AS clri ON (
|
2021-04-27 01:49:46 +00:00
|
|
|
clri.category_id = post.category_id
|
2021-04-25 19:33:22 +00:00
|
|
|
AND clri.user_id = $1
|
|
|
|
)
|
2021-05-11 22:53:23 +00:00
|
|
|
LEFT JOIN auth_user ON post.author_id = auth_user.id
|
|
|
|
LEFT JOIN handmade_libraryresource as lib_resource ON lib_resource.category_id = post.category_id
|
2021-04-25 19:33:22 +00:00
|
|
|
WHERE
|
2021-04-28 03:29:13 +00:00
|
|
|
post.category_kind = ANY ($2)
|
2021-04-29 04:52:27 +00:00
|
|
|
AND post.deleted = FALSE
|
2021-04-25 19:33:22 +00:00
|
|
|
AND post.thread_id IS NOT NULL
|
|
|
|
ORDER BY postdate DESC
|
2021-04-28 03:29:13 +00:00
|
|
|
LIMIT $3 OFFSET $4
|
2021-04-25 19:33:22 +00:00
|
|
|
`,
|
2021-05-30 18:35:01 +00:00
|
|
|
currentUserID,
|
2021-04-28 03:29:13 +00:00
|
|
|
[]models.CategoryKind{models.CatKindForum, models.CatKindBlog, models.CatKindWiki, models.CatKindLibraryResource},
|
2021-05-30 18:35:01 +00:00
|
|
|
limit,
|
|
|
|
offset,
|
2021-04-28 03:29:13 +00:00
|
|
|
)
|
2021-04-26 06:56:49 +00:00
|
|
|
c.Perf.EndBlock()
|
2021-04-25 19:33:22 +00:00
|
|
|
if err != nil {
|
2021-05-30 18:35:01 +00:00
|
|
|
return nil, err
|
2021-04-25 19:33:22 +00:00
|
|
|
}
|
|
|
|
|
2021-05-05 20:34:32 +00:00
|
|
|
c.Perf.StartBlock("FEED", "Build post items")
|
2021-04-25 19:33:22 +00:00
|
|
|
var postItems []templates.PostListItem
|
|
|
|
for _, iPostResult := range posts.ToSlice() {
|
|
|
|
postResult := iPostResult.(*feedPostQuery)
|
|
|
|
|
|
|
|
hasRead := false
|
|
|
|
if postResult.ThreadLastReadTime != nil && postResult.ThreadLastReadTime.After(postResult.Post.PostDate) {
|
|
|
|
hasRead = true
|
|
|
|
} else if postResult.CatLastReadTime != nil && postResult.CatLastReadTime.After(postResult.Post.PostDate) {
|
|
|
|
hasRead = true
|
|
|
|
}
|
|
|
|
|
2021-05-30 18:35:01 +00:00
|
|
|
postItem := MakePostListItem(
|
2021-05-11 22:53:23 +00:00
|
|
|
lineageBuilder,
|
|
|
|
&postResult.Proj,
|
|
|
|
&postResult.Thread,
|
|
|
|
&postResult.Post,
|
|
|
|
&postResult.User,
|
|
|
|
postResult.LibraryResource,
|
|
|
|
!hasRead,
|
|
|
|
true,
|
2021-05-25 13:12:20 +00:00
|
|
|
c.Theme,
|
2021-05-30 18:35:01 +00:00
|
|
|
)
|
2021-04-25 19:33:22 +00:00
|
|
|
|
2021-05-30 18:35:01 +00:00
|
|
|
postItem.UUID = uuid.NewSHA1(uuid.NameSpaceURL, []byte(postItem.Url)).URN()
|
2021-07-04 21:24:48 +00:00
|
|
|
postItem.LastEditDate = postResult.PostVersion.Date
|
2021-04-25 19:33:22 +00:00
|
|
|
|
2021-05-30 18:35:01 +00:00
|
|
|
postItems = append(postItems, postItem)
|
|
|
|
}
|
|
|
|
c.Perf.EndBlock()
|
2021-04-25 19:33:22 +00:00
|
|
|
|
2021-05-30 18:35:01 +00:00
|
|
|
return postItems, nil
|
2021-04-25 19:33:22 +00:00
|
|
|
}
|