Added projects atom feed and media file urls
This commit is contained in:
parent
5d9b628144
commit
63f1bf40cc
|
@ -52,6 +52,10 @@ func TestAtomFeed(t *testing.T) {
|
|||
AssertRegexMatch(t, BuildAtomFeed(), RegexAtomFeed, nil)
|
||||
AssertRegexMatch(t, BuildAtomFeedForProjects(), RegexAtomFeed, map[string]string{"feedtype": "projects"})
|
||||
AssertRegexMatch(t, BuildAtomFeedForShowcase(), RegexAtomFeed, map[string]string{"feedtype": "showcase"})
|
||||
|
||||
// NOTE(asaf): The following tests are for backwards compatibity
|
||||
AssertRegexMatch(t, "/atom/projects/new", RegexAtomFeed, map[string]string{"feedtype": "projects"})
|
||||
AssertRegexMatch(t, "/atom/showcase/new", RegexAtomFeed, map[string]string{"feedtype": "showcase"})
|
||||
}
|
||||
|
||||
func TestLoginAction(t *testing.T) {
|
||||
|
@ -343,6 +347,7 @@ func TestPublic(t *testing.T) {
|
|||
assert.Panics(t, func() { BuildPublic("/thing/image.png?hello", false) })
|
||||
|
||||
AssertRegexMatch(t, BuildTheme("test.css", "light", true), RegexPublic, nil)
|
||||
AssertRegexMatch(t, BuildUserFile("mylogo.png"), RegexPublic, nil)
|
||||
}
|
||||
|
||||
func TestMarkRead(t *testing.T) {
|
||||
|
|
|
@ -169,7 +169,7 @@ func BuildFeedWithPage(page int) string {
|
|||
return Url("/feed/"+strconv.Itoa(page), nil)
|
||||
}
|
||||
|
||||
var RegexAtomFeed = regexp.MustCompile("^/atom(/(?P<feedtype>.+))?$")
|
||||
var RegexAtomFeed = regexp.MustCompile("^/atom(/(?P<feedtype>[^/]+))?(/new)?$") // NOTE(asaf): `/new` for backwards compatibility with old website
|
||||
|
||||
func BuildAtomFeed() string {
|
||||
defer CatchPanic()
|
||||
|
@ -682,10 +682,16 @@ func BuildPublic(filepath string, cachebust bool) string {
|
|||
|
||||
func BuildTheme(filepath string, theme string, cachebust bool) string {
|
||||
defer CatchPanic()
|
||||
filepath = strings.Trim(filepath, "/")
|
||||
if len(theme) == 0 {
|
||||
panic(oops.New(nil, "Theme can't be blank"))
|
||||
}
|
||||
return BuildPublic(fmt.Sprintf("themes/%s/%s", theme, strings.Trim(filepath, "/")), cachebust)
|
||||
return BuildPublic(fmt.Sprintf("themes/%s/%s", theme, filepath), cachebust)
|
||||
}
|
||||
|
||||
func BuildUserFile(filepath string) string {
|
||||
filepath = strings.Trim(filepath, "/")
|
||||
return BuildPublic(fmt.Sprintf("media/%s", filepath), false)
|
||||
}
|
||||
|
||||
/*
|
||||
|
|
|
@ -39,6 +39,10 @@ type Project struct {
|
|||
Color1 string `db:"color_1"`
|
||||
Color2 string `db:"color_2"`
|
||||
|
||||
LogoLight string `db:"logolight"`
|
||||
LogoDark string `db:"logodark"`
|
||||
|
||||
DateApproved time.Time `db:"date_approved"`
|
||||
AllLastUpdated time.Time `db:"all_last_updated"`
|
||||
}
|
||||
|
||||
|
|
|
@ -58,6 +58,10 @@ func ProjectToTemplate(p *models.Project) Project {
|
|||
Color1: p.Color1,
|
||||
Color2: p.Color2,
|
||||
Url: hmnurl.BuildProjectHomepage(p.Slug),
|
||||
Blurb: p.Blurb,
|
||||
|
||||
LogoLight: hmnurl.BuildUserFile(p.LogoLight),
|
||||
LogoDark: hmnurl.BuildUserFile(p.LogoDark),
|
||||
|
||||
IsHMN: p.IsHMN(),
|
||||
|
||||
|
@ -65,6 +69,8 @@ func ProjectToTemplate(p *models.Project) Project {
|
|||
HasForum: true,
|
||||
HasWiki: true,
|
||||
HasLibrary: true,
|
||||
|
||||
DateApproved: p.DateApproved,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -78,10 +84,13 @@ func ThreadToTemplate(t *models.Thread) Thread {
|
|||
|
||||
func UserToTemplate(u *models.User, currentTheme string) User {
|
||||
// TODO: Handle deleted users. Maybe not here, but if not, at call sites of this function.
|
||||
if currentTheme == "" {
|
||||
currentTheme = "light"
|
||||
}
|
||||
|
||||
avatar := ""
|
||||
if u.Avatar != nil && len(*u.Avatar) > 0 {
|
||||
avatar = hmnurl.BuildPublic(*u.Avatar, false)
|
||||
avatar = hmnurl.BuildUserFile(*u.Avatar)
|
||||
} else {
|
||||
avatar = hmnurl.BuildTheme("empty-avatar.svg", currentTheme, true)
|
||||
}
|
||||
|
|
|
@ -26,6 +26,20 @@
|
|||
{{ end }}
|
||||
{{ else if .Projects }}
|
||||
{{ range .Projects }}
|
||||
<entry>
|
||||
<title>{{ .Name }}</title>
|
||||
<link rel="alternate" type="text/html" href="{{ .Url }}" />
|
||||
<id>{{ .UUID }}</id>
|
||||
<published>{{ rfc3339 .DateApproved }}</published>
|
||||
{{ range .Owners }}
|
||||
<author>
|
||||
<name>{{ .Name }}</name>
|
||||
<uri>{{ .ProfileUrl }}</uri>
|
||||
</author>
|
||||
{{ end }}
|
||||
<logo>{{ .LogoLight }}</logo>
|
||||
<summary type="html">{{ .Blurb }}</summary>
|
||||
</entry>
|
||||
{{ end }}
|
||||
{{ else if .Snippets }}
|
||||
{{ range .Snippets }}
|
||||
|
|
|
@ -91,6 +91,11 @@ type Project struct {
|
|||
Color1 string
|
||||
Color2 string
|
||||
Url string
|
||||
Blurb string
|
||||
Owners []User
|
||||
|
||||
LogoDark string
|
||||
LogoLight string
|
||||
|
||||
IsHMN bool
|
||||
|
||||
|
@ -98,6 +103,9 @@ type Project struct {
|
|||
HasForum bool
|
||||
HasWiki bool
|
||||
HasLibrary bool
|
||||
|
||||
UUID string
|
||||
DateApproved time.Time
|
||||
}
|
||||
|
||||
type User struct {
|
||||
|
|
|
@ -136,7 +136,7 @@ type AtomFeedData struct {
|
|||
|
||||
FeedType FeedType
|
||||
Posts []templates.PostListItem
|
||||
Projects []int // TODO(asaf): Actually do this
|
||||
Projects []templates.Project
|
||||
Snippets []int // TODO(asaf): Actually do this
|
||||
}
|
||||
|
||||
|
@ -178,7 +178,68 @@ func AtomFeed(c *RequestContext) ResponseData {
|
|||
} else {
|
||||
switch strings.ToLower(feedType) {
|
||||
case "projects":
|
||||
// TODO(asaf): Implement this
|
||||
feedData.Title = "New Projects | Site-wide | Handmade.Network"
|
||||
feedData.Subtitle = feedData.Title
|
||||
feedData.FeedType = FeedTypeProjects
|
||||
feedData.FeedID = FeedIDProjects
|
||||
feedData.AtomFeedUrl = hmnurl.BuildAtomFeedForProjects()
|
||||
feedData.FeedUrl = hmnurl.BuildProjectIndex()
|
||||
|
||||
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
|
||||
FROM handmade_project AS project
|
||||
ORDER BY date_approved DESC
|
||||
LIMIT $1
|
||||
`,
|
||||
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
|
||||
templateProject := templates.ProjectToTemplate(&project)
|
||||
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()
|
||||
case "showcase":
|
||||
// TODO(asaf): Implement this
|
||||
default:
|
||||
|
|
Loading…
Reference in New Issue