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"
|
2021-05-30 18:35:01 +00:00
|
|
|
"strings"
|
2021-04-25 19:33:22 +00:00
|
|
|
"time"
|
|
|
|
|
2021-12-09 02:04:15 +00:00
|
|
|
"git.handmade.network/hmn/hmn/src/hmndata"
|
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-12-09 02:04:15 +00:00
|
|
|
"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 {
|
2021-12-09 02:04:15 +00:00
|
|
|
numPosts, err := hmndata.CountPosts(c.Context(), c.Conn, c.CurrentUser, hmndata.PostsQuery{
|
2021-10-24 20:48:28 +00:00
|
|
|
ThreadTypes: feedThreadTypes,
|
2021-09-23 20:02:45 +00:00
|
|
|
})
|
2021-04-25 19:33:22 +00:00
|
|
|
if err != nil {
|
2021-09-23 20:02:45 +00:00
|
|
|
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)
|
2021-09-23 20:02:45 +00:00
|
|
|
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)
|
2021-05-30 18:35:01 +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 posts"))
|
2021-05-30 18:35:01 +00:00
|
|
|
}
|
|
|
|
|
2021-09-01 18:25:09 +00:00
|
|
|
baseData := getBaseDataAutocrumb(c, "Feed")
|
2021-05-30 18:35:01 +00:00
|
|
|
baseData.BodyClasses = append(baseData.BodyClasses, "feed")
|
|
|
|
|
|
|
|
var res ResponseData
|
2021-07-17 15:19:17 +00:00
|
|
|
res.MustWriteTemplate("feed.html", FeedData{
|
2021-05-30 18:35:01 +00:00
|
|
|
BaseData: baseData,
|
|
|
|
|
|
|
|
AtomFeedUrl: hmnurl.BuildAtomFeed(),
|
2021-11-10 04:11:39 +00:00
|
|
|
MarkAllReadUrl: c.UrlContext.BuildForumMarkRead(0),
|
2021-05-30 18:35:01 +00:00
|
|
|
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
|
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()
|
|
|
|
|
2021-09-23 20:02:45 +00:00
|
|
|
posts, err := fetchAllPosts(c, 0, itemsPerFeed)
|
2021-05-30 18:35:01 +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 posts"))
|
2021-05-30 18:35:01 +00:00
|
|
|
}
|
|
|
|
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")
|
2021-09-05 19:43:34 +00:00
|
|
|
_, hasAll := c.Req.URL.Query()["all"]
|
|
|
|
if hasAll {
|
|
|
|
itemsPerFeed = 100000
|
|
|
|
}
|
2021-12-09 02:04:15 +00:00
|
|
|
projectsAndStuff, err := hmndata.FetchProjects(c.Context(), c.Conn, nil, hmndata.ProjectsQuery{
|
2021-12-02 10:53:36 +00:00
|
|
|
Lifecycles: models.VisibleProjectLifecycles,
|
|
|
|
Limit: itemsPerFeed,
|
2021-12-09 02:04:15 +00:00
|
|
|
Types: hmndata.OfficialProjects,
|
2021-12-02 10:53:36 +00:00
|
|
|
OrderBy: "date_approved DESC",
|
|
|
|
})
|
2021-05-31 23:23:04 +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-05-31 23:23:04 +00:00
|
|
|
}
|
2021-12-02 10:53:36 +00:00
|
|
|
for _, p := range projectsAndStuff {
|
2021-12-09 02:04:15 +00:00
|
|
|
templateProject := templates.ProjectToTemplate(&p.Project, hmndata.UrlContextForProject(&p.Project).BuildHomepage())
|
2021-05-31 23:23:04 +00:00
|
|
|
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, ""))
|
|
|
|
}
|
2021-05-31 23:23:04 +00:00
|
|
|
|
|
|
|
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
|
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()
|
|
|
|
|
2021-12-09 02:04:15 +00:00
|
|
|
snippets, err := hmndata.FetchSnippets(c.Context(), 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 {
|
|
|
|
timelineItem := SnippetToTimelineItem(&s.Snippet, s.Asset, s.DiscordMessage, s.Tags, s.Owner, c.Theme)
|
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
|
2021-05-30 18:35:01 +00:00
|
|
|
default:
|
|
|
|
return FourOhFour(c)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
var res ResponseData
|
2021-07-17 15:19:17 +00:00
|
|
|
res.MustWriteTemplate("atom.xml", feedData, c.Perf)
|
2021-05-30 18:35:01 +00:00
|
|
|
return res
|
|
|
|
}
|
|
|
|
|
2021-09-23 20:02:45 +00:00
|
|
|
func fetchAllPosts(c *RequestContext, offset int, limit int) ([]templates.PostListItem, error) {
|
2021-12-09 02:04:15 +00:00
|
|
|
postsAndStuff, err := hmndata.FetchPosts(c.Context(), c.Conn, c.CurrentUser, hmndata.PostsQuery{
|
2021-10-24 20:48:28 +00:00
|
|
|
ThreadTypes: feedThreadTypes,
|
2021-09-23 20:02:45 +00:00
|
|
|
Limit: limit,
|
|
|
|
Offset: offset,
|
|
|
|
SortDescending: true,
|
|
|
|
})
|
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-09-23 20:02:45 +00:00
|
|
|
c.Perf.StartBlock("SQL", "Fetch subforum tree")
|
|
|
|
subforumTree := models.GetFullSubforumTree(c.Context(), 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
|
2021-09-23 20:02:45 +00:00
|
|
|
for _, postAndStuff := range postsAndStuff {
|
2021-05-30 18:35:01 +00:00
|
|
|
postItem := MakePostListItem(
|
2021-05-11 22:53:23 +00:00
|
|
|
lineageBuilder,
|
2021-09-23 20:02:45 +00:00
|
|
|
&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,
|
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-09-23 20:02:45 +00:00
|
|
|
postItem.LastEditDate = postAndStuff.CurrentVersion.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
|
|
|
}
|