hmn/src/website/feed.go

255 lines
7.2 KiB
Go
Raw Normal View History

2021-04-25 19:33:22 +00:00
package website
import (
"fmt"
2021-04-25 19:33:22 +00:00
"math"
"net/http"
"strings"
2021-04-25 19:33:22 +00:00
"time"
"git.handmade.network/hmn/hmn/src/hmndata"
"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"
"github.com/google/uuid"
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
}
2021-10-24 20:48:28 +00:00
const feedPostsPerPage = 30
var feedThreadTypes = []models.ThreadType{
models.ThreadTypeForumPost,
models.ThreadTypeProjectBlogPost,
}
2021-04-25 19:33:22 +00:00
2021-10-24 20:48:28 +00:00
func Feed(c *RequestContext) ResponseData {
numPosts, err := hmndata.CountPosts(c, c.Conn, c.CurrentUser, hmndata.PostsQuery{
2021-10-24 20:48:28 +00:00
ThreadTypes: feedThreadTypes,
})
2021-04-25 19:33:22 +00:00
if err != nil {
return c.ErrorResponse(http.StatusInternalServerError, err)
2021-04-25 19:33:22 +00:00
}
2021-10-24 20:48:28 +00:00
numPages := int(math.Ceil(float64(numPosts) / feedPostsPerPage))
2021-04-25 19:33:22 +00:00
2021-10-24 20:48:28 +00:00
page, numPages, ok := getPageInfo(c.PathParams["page"], numPosts, feedPostsPerPage)
if !ok {
return c.Redirect(hmnurl.BuildFeed(), http.StatusSeeOther)
2021-04-25 19:33:22 +00:00
}
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
}
2021-10-24 20:48:28 +00:00
posts, err := fetchAllPosts(c, (page-1)*feedPostsPerPage, feedPostsPerPage)
if err != nil {
2021-08-28 12:21:03 +00:00
return c.ErrorResponse(http.StatusInternalServerError, oops.New(err, "failed to fetch feed posts"))
}
2021-09-01 18:25:09 +00:00
baseData := getBaseDataAutocrumb(c, "Feed")
baseData.BodyClasses = append(baseData.BodyClasses, "feed")
var res ResponseData
res.MustWriteTemplate("feed.html", FeedData{
BaseData: baseData,
AtomFeedUrl: hmnurl.BuildAtomFeed(),
MarkAllReadUrl: c.UrlContext.BuildForumMarkRead(0),
Posts: posts,
Pagination: pagination,
}, c.Perf)
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
Projects []templates.Project
2021-06-22 12:02:47 +00:00
Snippets []templates.TimelineItem
}
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()
posts, err := fetchAllPosts(c, 0, itemsPerFeed)
if err != nil {
2021-08-28 12:21:03 +00:00
return c.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":
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)
c.Perf.StartBlock("SQL", "Fetching projects")
2021-09-05 19:43:34 +00:00
_, hasAll := c.Req.URL.Query()["all"]
if hasAll {
itemsPerFeed = 100000
}
projectsAndStuff, err := hmndata.FetchProjects(c, c.Conn, nil, hmndata.ProjectsQuery{
2021-12-11 19:08:10 +00:00
Limit: itemsPerFeed,
Types: hmndata.OfficialProjects,
OrderBy: "date_approved DESC",
2021-12-02 10:53:36 +00:00
})
if err != nil {
2021-08-28 12:21:03 +00:00
return c.ErrorResponse(http.StatusInternalServerError, oops.New(err, "failed to fetch feed projects"))
}
2021-12-02 10:53:36 +00:00
for _, p := range projectsAndStuff {
templateProject := templates.ProjectToTemplate(&p.Project, hmndata.UrlContextForProject(&p.Project).BuildHomepage())
templateProject.UUID = uuid.NewSHA1(uuid.NameSpaceURL, []byte(templateProject.Url)).URN()
2021-12-02 10:53:36 +00:00
for _, owner := range p.Owners {
templateProject.Owners = append(templateProject.Owners, templates.UserToTemplate(owner, ""))
}
feedData.Projects = append(feedData.Projects, templateProject)
}
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
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()
snippets, err := hmndata.FetchSnippets(c, c.Conn, c.CurrentUser, hmndata.SnippetQuery{
2021-11-11 19:00:46 +00:00
Limit: itemsPerFeed,
})
2021-06-22 12:02:47 +00:00
if err != nil {
2021-08-28 12:21:03 +00:00
return c.ErrorResponse(http.StatusInternalServerError, oops.New(err, "failed to fetch snippets"))
2021-06-22 12:02:47 +00:00
}
2021-11-11 19:00:46 +00:00
for _, s := range snippets {
2022-08-05 04:03:45 +00:00
timelineItem := SnippetToTimelineItem(&s.Snippet, s.Asset, s.DiscordMessage, s.Projects, s.Owner, c.Theme, false)
2021-06-22 12:02:47 +00:00
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
default:
return FourOhFour(c)
}
}
var res ResponseData
res.MustWriteTemplate("atom.xml", feedData, c.Perf)
return res
}
func fetchAllPosts(c *RequestContext, offset int, limit int) ([]templates.PostListItem, error) {
postsAndStuff, err := hmndata.FetchPosts(c, c.Conn, c.CurrentUser, hmndata.PostsQuery{
2021-10-24 20:48:28 +00:00
ThreadTypes: feedThreadTypes,
Limit: limit,
Offset: offset,
SortDescending: true,
})
2021-04-25 19:33:22 +00:00
if err != nil {
return nil, err
2021-04-25 19:33:22 +00:00
}
c.Perf.StartBlock("SQL", "Fetch subforum tree")
subforumTree := models.GetFullSubforumTree(c, c.Conn)
lineageBuilder := models.MakeSubforumLineageBuilder(subforumTree)
c.Perf.EndBlock()
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 _, postAndStuff := range postsAndStuff {
postItem := MakePostListItem(
2021-05-11 22:53:23 +00:00
lineageBuilder,
&postAndStuff.Project,
&postAndStuff.Thread,
&postAndStuff.Post,
postAndStuff.Author,
2021-09-24 00:15:29 +00:00
postAndStuff.Unread,
2021-05-11 22:53:23 +00:00
true,
c.Theme,
)
2021-04-25 19:33:22 +00:00
postItem.UUID = uuid.NewSHA1(uuid.NameSpaceURL, []byte(postItem.Url)).URN()
postItem.LastEditDate = postAndStuff.CurrentVersion.Date
2021-04-25 19:33:22 +00:00
postItems = append(postItems, postItem)
}
c.Perf.EndBlock()
2021-04-25 19:33:22 +00:00
return postItems, nil
2021-04-25 19:33:22 +00:00
}