hmn/src/hmnurl/urls.go

1066 lines
27 KiB
Go
Raw Normal View History

2021-05-05 20:34:32 +00:00
package hmnurl
import (
"fmt"
2021-05-11 22:53:23 +00:00
"net/url"
2021-05-05 20:34:32 +00:00
"regexp"
"strconv"
"strings"
2021-05-06 04:04:58 +00:00
"git.handmade.network/hmn/hmn/src/logging"
"git.handmade.network/hmn/hmn/src/models"
2021-05-06 04:04:58 +00:00
"git.handmade.network/hmn/hmn/src/oops"
2021-05-05 20:34:32 +00:00
)
/*
Any function in this package whose name starts with Build is required to be covered by a test.
This helps ensure that we don't generate URLs that can't be routed.
*/
2021-08-08 20:05:52 +00:00
var RegexOldHome = regexp.MustCompile("^/home$")
var RegexHomepage = regexp.MustCompile("^/$")
2021-05-05 20:34:32 +00:00
func BuildHomepage() string {
return HMNProjectContext.BuildHomepage()
2021-05-05 20:34:32 +00:00
}
func (c *UrlContext) BuildHomepage() string {
return c.Url("/", nil)
2021-05-11 22:53:23 +00:00
}
var RegexShowcase = regexp.MustCompile("^/showcase$")
func BuildShowcase() string {
defer CatchPanic()
2021-05-11 22:53:23 +00:00
return Url("/showcase", nil)
}
var RegexStreams = regexp.MustCompile("^/streams$")
func BuildStreams() string {
defer CatchPanic()
2021-05-11 22:53:23 +00:00
return Url("/streams", nil)
}
var RegexWhenIsIt = regexp.MustCompile("^/whenisit$")
func BuildWhenIsIt() string {
defer CatchPanic()
return Url("/whenisit", nil)
}
2021-08-28 11:26:17 +00:00
var RegexJamIndex = regexp.MustCompile("^/jam$")
func BuildJamIndex() string {
defer CatchPanic()
return Url("/jam", nil)
}
2022-06-19 22:26:33 +00:00
var RegexJamIndex2021 = regexp.MustCompile("^/jam/2021$")
func BuildJamIndex2021() string {
defer CatchPanic()
return Url("/jam/2021", nil)
}
var RegexJamIndex2022 = regexp.MustCompile("^/jam/2022$")
func BuildJamIndex2022() string {
defer CatchPanic()
return Url("/jam/2022", nil)
}
var RegexJamFeed2022 = regexp.MustCompile("^/jam/2022/feed$")
func BuildJamFeed2022() string {
defer CatchPanic()
return Url("/jam/2022/feed", nil)
}
2022-06-17 22:30:18 +00:00
// QUESTION(ben): Can we change these routes?
2021-05-11 22:53:23 +00:00
var RegexLoginAction = regexp.MustCompile("^/login$")
func BuildLoginAction(redirectTo string) string {
defer CatchPanic()
2021-05-11 22:53:23 +00:00
return Url("/login", []Q{{Name: "redirect", Value: redirectTo}})
}
2021-08-08 20:05:52 +00:00
var RegexLoginPage = regexp.MustCompile("^/login$")
2021-05-11 22:53:23 +00:00
func BuildLoginPage(redirectTo string) string {
defer CatchPanic()
2021-08-08 20:05:52 +00:00
return Url("/login", []Q{{Name: "redirect", Value: redirectTo}})
2021-05-11 22:53:23 +00:00
}
var RegexLogoutAction = regexp.MustCompile("^/logout$")
func BuildLogoutAction(redir string) string {
defer CatchPanic()
if redir == "" {
redir = "/"
}
return Url("/logout", []Q{{"redirect", redir}})
2021-05-05 20:34:32 +00:00
}
2021-08-08 20:05:52 +00:00
var RegexRegister = regexp.MustCompile("^/register$")
2021-06-06 23:48:43 +00:00
func BuildRegister(destination string) string {
2021-06-06 23:48:43 +00:00
defer CatchPanic()
var query []Q
if destination != "" {
query = append(query, Q{"destination", destination})
}
return Url("/register", query)
2021-08-08 20:05:52 +00:00
}
var RegexRegistrationSuccess = regexp.MustCompile("^/registered_successfully$")
func BuildRegistrationSuccess() string {
defer CatchPanic()
return Url("/registered_successfully", nil)
}
var RegexEmailConfirmation = regexp.MustCompile("^/email_confirmation/(?P<username>[^/]+)/(?P<token>[^/]+)$")
2022-08-13 19:15:00 +00:00
func BuildEmailConfirmation(username, token string, destination string) string {
2021-08-08 20:05:52 +00:00
defer CatchPanic()
2022-08-13 19:15:00 +00:00
var query []Q
if destination != "" {
query = append(query, Q{"destination", destination})
}
return Url(fmt.Sprintf("/email_confirmation/%s/%s", url.PathEscape(username), token), query)
2021-08-08 20:05:52 +00:00
}
2021-08-17 05:18:04 +00:00
var RegexRequestPasswordReset = regexp.MustCompile("^/password_reset$")
2021-08-08 20:05:52 +00:00
2021-08-17 05:18:04 +00:00
func BuildRequestPasswordReset() string {
2021-08-08 20:05:52 +00:00
defer CatchPanic()
return Url("/password_reset", nil)
2021-06-06 23:48:43 +00:00
}
2021-08-17 05:18:04 +00:00
var RegexPasswordResetSent = regexp.MustCompile("^/password_reset/sent$")
func BuildPasswordResetSent() string {
defer CatchPanic()
return Url("/password_reset/sent", nil)
}
var RegexOldDoPasswordReset = regexp.MustCompile(`^_password_reset/(?P<username>[\w\ \.\,\-@\+\_]+)/(?P<token>[\d\w]+)[\/]?$`)
var RegexDoPasswordReset = regexp.MustCompile("^/password_reset/(?P<username>[^/]+)/(?P<token>[^/]+)$")
func BuildDoPasswordReset(username string, token string) string {
defer CatchPanic()
return Url(fmt.Sprintf("/password_reset/%s/%s", url.PathEscape(username), token), nil)
}
2021-05-11 22:53:23 +00:00
/*
* Static Pages
*/
var RegexManifesto = regexp.MustCompile("^/manifesto$")
2021-05-05 20:34:32 +00:00
func BuildManifesto() string {
defer CatchPanic()
2021-05-05 20:34:32 +00:00
return Url("/manifesto", nil)
}
var RegexAbout = regexp.MustCompile("^/about$")
2021-05-05 20:34:32 +00:00
func BuildAbout() string {
defer CatchPanic()
2021-05-05 20:34:32 +00:00
return Url("/about", nil)
}
var RegexCommunicationGuidelines = regexp.MustCompile("^/communication-guidelines$")
2021-05-05 20:34:32 +00:00
func BuildCommunicationGuidelines() string {
defer CatchPanic()
2021-05-05 20:34:32 +00:00
return Url("/communication-guidelines", nil)
}
var RegexContactPage = regexp.MustCompile("^/contact$")
2021-05-05 20:34:32 +00:00
func BuildContactPage() string {
defer CatchPanic()
2021-05-05 20:34:32 +00:00
return Url("/contact", nil)
}
var RegexMonthlyUpdatePolicy = regexp.MustCompile("^/monthly-update-policy$")
2021-05-05 20:34:32 +00:00
func BuildMonthlyUpdatePolicy() string {
defer CatchPanic()
2021-05-05 20:34:32 +00:00
return Url("/monthly-update-policy", nil)
}
var RegexProjectSubmissionGuidelines = regexp.MustCompile("^/project-guidelines$")
2021-05-05 20:34:32 +00:00
func BuildProjectSubmissionGuidelines() string {
defer CatchPanic()
2021-05-05 20:34:32 +00:00
return Url("/project-guidelines", nil)
}
var RegexConferences = regexp.MustCompile("^/conferences$")
func BuildConferences() string {
defer CatchPanic()
return Url("/conferences", nil)
}
2021-05-11 22:53:23 +00:00
/*
2021-06-22 09:50:40 +00:00
* User
2021-05-11 22:53:23 +00:00
*/
2021-06-22 09:50:40 +00:00
var RegexUserProfile = regexp.MustCompile(`^/m/(?P<username>[^/]+)$`)
2021-05-11 22:53:23 +00:00
2021-06-22 09:50:40 +00:00
func BuildUserProfile(username string) string {
defer CatchPanic()
2021-05-11 22:53:23 +00:00
if len(username) == 0 {
panic(oops.New(nil, "Username must not be blank"))
}
2021-12-21 04:24:05 +00:00
return Url("/m/"+username, nil)
2021-08-08 20:05:52 +00:00
}
var RegexUserSettings = regexp.MustCompile(`^/settings$`)
2021-08-17 05:18:04 +00:00
func BuildUserSettings(section string) string {
return UrlWithFragment("/settings", nil, section)
2021-05-11 22:53:23 +00:00
}
2021-09-24 00:12:46 +00:00
/*
* Admin
*/
var RegexAdminAtomFeed = regexp.MustCompile(`^/admin/atom$`)
func BuildAdminAtomFeed() string {
defer CatchPanic()
return Url("/admin/atom", nil)
}
var RegexAdminApprovalQueue = regexp.MustCompile(`^/admin/approvals$`)
func BuildAdminApprovalQueue() string {
defer CatchPanic()
return Url("/admin/approvals", nil)
}
var RegexAdminSetUserOptions = regexp.MustCompile(`^/admin/setuseroptions$`)
2021-12-15 01:17:42 +00:00
func BuildAdminSetUserOptions() string {
2021-12-15 01:17:42 +00:00
defer CatchPanic()
return Url("/admin/setuseroptions", nil)
2021-12-15 01:17:42 +00:00
}
var RegexAdminNukeUser = regexp.MustCompile(`^/admin/nukeuser$`)
func BuildAdminNukeUser() string {
defer CatchPanic()
return Url("/admin/nukeuser", nil)
}
2021-06-22 09:50:40 +00:00
/*
* Snippets
*/
var RegexSnippet = regexp.MustCompile(`^/snippet/(?P<snippetid>\d+)$`)
func BuildSnippet(snippetId int) string {
defer CatchPanic()
return Url("/snippet/"+strconv.Itoa(snippetId), nil)
}
2022-08-05 04:03:45 +00:00
var RegexSnippetSubmit = regexp.MustCompile(`^/snippet$`)
func BuildSnippetSubmit() string {
defer CatchPanic()
return Url("/snippet", nil)
}
2021-05-11 22:53:23 +00:00
/*
* Feed
*/
var RegexFeed = regexp.MustCompile(`^/feed(/(?P<page>.+)?)?$`)
2021-05-05 20:34:32 +00:00
func BuildFeed() string {
defer CatchPanic()
2021-05-05 20:34:32 +00:00
return Url("/feed", nil)
}
func BuildFeedWithPage(page int) string {
defer CatchPanic()
2021-05-05 20:34:32 +00:00
if page < 1 {
panic(oops.New(nil, "Invalid feed page (%d), must be >= 1", page))
}
if page == 1 {
return BuildFeed()
}
return Url("/feed/"+strconv.Itoa(page), nil)
}
var RegexAtomFeed = regexp.MustCompile("^/atom(/(?P<feedtype>[^/]+))?(/new)?$") // NOTE(asaf): `/new` for backwards compatibility with old website
func BuildAtomFeed() string {
defer CatchPanic()
return Url("/atom", nil)
}
func BuildAtomFeedForProjects() string {
defer CatchPanic()
return Url("/atom/projects", nil)
}
func BuildAtomFeedForShowcase() string {
defer CatchPanic()
return Url("/atom/showcase", nil)
}
2021-06-06 23:48:43 +00:00
/*
* Projects
*/
var RegexProjectIndex = regexp.MustCompile("^/projects(/(?P<page>.+)?)?$")
func BuildProjectIndex(page int) string {
defer CatchPanic()
if page < 1 {
panic(oops.New(nil, "page must be >= 1"))
}
if page == 1 {
return Url("/projects", nil)
} else {
return Url(fmt.Sprintf("/projects/%d", page), nil)
}
}
2021-11-25 03:59:51 +00:00
var RegexProjectNew = regexp.MustCompile("^/p/new$")
2021-06-06 23:48:43 +00:00
func BuildProjectNew() string {
defer CatchPanic()
2021-11-25 03:59:51 +00:00
return Url("/p/new", nil)
2021-06-06 23:48:43 +00:00
}
2022-06-25 13:24:04 +00:00
func BuildProjectNewJam() string {
defer CatchPanic()
return Url("/p/new", []Q{{Name: "jam", Value: "1"}})
2022-06-25 13:24:04 +00:00
}
2021-11-10 17:34:48 +00:00
var RegexPersonalProject = regexp.MustCompile("^/p/(?P<projectid>[0-9]+)(/(?P<projectslug>[a-zA-Z0-9-]+))?")
2021-06-06 23:48:43 +00:00
func BuildPersonalProject(id int, slug string) string {
2021-06-06 23:48:43 +00:00
defer CatchPanic()
2021-11-08 19:16:54 +00:00
return Url(fmt.Sprintf("/p/%d/%s", id, slug), nil)
2021-06-06 23:48:43 +00:00
}
var RegexProjectEdit = regexp.MustCompile("^/edit$")
2021-07-08 07:40:30 +00:00
func (c *UrlContext) BuildProjectEdit(section string) string {
2021-07-08 07:40:30 +00:00
defer CatchPanic()
return c.UrlWithFragment("/edit", nil, section)
2021-07-08 07:40:30 +00:00
}
2021-05-11 22:53:23 +00:00
/*
* Podcast
*/
var RegexPodcast = regexp.MustCompile(`^/podcast$`)
func BuildPodcast() string {
defer CatchPanic()
return Url("/podcast", nil)
2021-05-11 22:53:23 +00:00
}
2021-07-23 03:09:46 +00:00
var RegexPodcastEdit = regexp.MustCompile(`^/podcast/edit$`)
func BuildPodcastEdit() string {
2021-07-23 03:09:46 +00:00
defer CatchPanic()
return Url("/podcast/edit", nil)
2021-07-23 03:09:46 +00:00
}
var RegexPodcastEpisode = regexp.MustCompile(`^/podcast/ep/(?P<episodeid>[^/]+)$`)
func BuildPodcastEpisode(episodeGUID string) string {
2021-07-23 03:09:46 +00:00
defer CatchPanic()
return Url(fmt.Sprintf("/podcast/ep/%s", episodeGUID), nil)
2021-07-23 03:09:46 +00:00
}
var RegexPodcastEpisodeNew = regexp.MustCompile(`^/podcast/ep/new$`)
func BuildPodcastEpisodeNew() string {
2021-07-23 03:09:46 +00:00
defer CatchPanic()
return Url("/podcast/ep/new", nil)
2021-07-23 03:09:46 +00:00
}
var RegexPodcastEpisodeEdit = regexp.MustCompile(`^/podcast/ep/(?P<episodeid>[^/]+)/edit$`)
func BuildPodcastEpisodeEdit(episodeGUID string) string {
2021-07-23 03:09:46 +00:00
defer CatchPanic()
return Url(fmt.Sprintf("/podcast/ep/%s/edit", episodeGUID), nil)
2021-07-23 03:09:46 +00:00
}
var RegexPodcastRSS = regexp.MustCompile(`^/podcast/podcast.xml$`)
func BuildPodcastRSS() string {
2021-07-23 03:09:46 +00:00
defer CatchPanic()
return Url("/podcast/podcast.xml", nil)
2021-07-23 03:09:46 +00:00
}
func BuildPodcastEpisodeFile(filename string) string {
2021-07-23 03:09:46 +00:00
defer CatchPanic()
return BuildUserFile(fmt.Sprintf("podcast/%s/%s", models.HMNProjectSlug, filename))
2021-07-23 03:09:46 +00:00
}
/*
* Fishbowls
*/
var RegexFishbowlIndex = regexp.MustCompile(`^/fishbowl$`)
func BuildFishbowlIndex() string {
defer CatchPanic()
return Url("/fishbowl", nil)
}
var RegexFishbowl = regexp.MustCompile(`^/fishbowl/(?P<slug>[^/]+)/?$`)
func BuildFishbowl(slug string) string {
defer CatchPanic()
return Url(fmt.Sprintf("/fishbowl/%s/", slug), nil)
}
var RegexFishbowlFiles = regexp.MustCompile(`^/fishbowl/(?P<slug>[^/]+)(?P<path>/.+)$`)
/*
* Education
*/
var RegexEducationIndex = regexp.MustCompile(`^/education$`)
func BuildEducationIndex() string {
defer CatchPanic()
return Url("/education", nil)
}
var RegexEducationGlossary = regexp.MustCompile(`^/education/glossary(/(?P<slug>[^/]+))?$`)
func BuildEducationGlossary(termSlug string) string {
defer CatchPanic()
if termSlug == "" {
return Url("/education/glossary", nil)
} else {
return Url(fmt.Sprintf("/education/glossary/%s", termSlug), nil)
}
}
var RegexEducationArticle = regexp.MustCompile(`^/education/(?P<slug>[^/]+)$`)
func BuildEducationArticle(slug string) string {
return Url(fmt.Sprintf("/education/%s", slug), nil)
}
var RegexEducationArticleNew = regexp.MustCompile(`^/education/new$`)
func BuildEducationArticleNew() string {
return Url("/education/new", nil)
}
var RegexEducationArticleEdit = regexp.MustCompile(`^/education/(?P<slug>[^/]+)/edit$`)
func BuildEducationArticleEdit(slug string) string {
return Url(fmt.Sprintf("/education/%s/edit", slug), nil)
}
var RegexEducationArticleDelete = regexp.MustCompile(`^/education/(?P<slug>[^/]+)/delete$`)
func BuildEducationArticleDelete(slug string) string {
return Url(fmt.Sprintf("/education/%s/delete", slug), nil)
}
var RegexEducationRerender = regexp.MustCompile(`^/education/rerender$`)
func BuildEducationRerender() string {
return Url("/education/rerender", nil)
}
2021-05-11 22:53:23 +00:00
/*
* Forums
*/
2021-08-28 17:07:45 +00:00
// NOTE(asaf): This also matches urls generated by BuildForumThread (/t/ is identified as a subforum, and the threadid as a page)
// Make sure to match Thread before Subforum in the router.
var RegexForum = regexp.MustCompile(`^/forums(/(?P<subforums>[^\d/]+(/[^\d]+)*))?(/(?P<page>\d+))?$`)
2021-05-05 20:34:32 +00:00
func (c *UrlContext) Url(path string, query []Q) string {
return c.UrlWithFragment(path, query, "")
}
func (c *UrlContext) UrlWithFragment(path string, query []Q, fragment string) string {
if c == nil {
logging.Warn().Stack().Msg("URL context was nil; defaulting to the HMN URL context")
c = &HMNProjectContext
}
if c.PersonalProject {
url := url.URL{
Scheme: baseUrlParsed.Scheme,
Host: baseUrlParsed.Host,
Path: fmt.Sprintf("p/%d/%s/%s", c.ProjectID, models.GeneratePersonalProjectSlug(c.ProjectName), trim(path)),
RawQuery: encodeQuery(query),
Fragment: fragment,
}
return url.String()
} else {
subdomain := c.ProjectSlug
if c.ProjectSlug == models.HMNProjectSlug {
subdomain = ""
}
host := baseUrlParsed.Host
if len(subdomain) > 0 {
host = c.ProjectSlug + "." + host
}
url := url.URL{
Scheme: baseUrlParsed.Scheme,
Host: host,
Path: trim(path),
RawQuery: encodeQuery(query),
Fragment: fragment,
}
return url.String()
}
}
func (c *UrlContext) BuildForum(subforums []string, page int) string {
defer CatchPanic()
2021-05-05 20:34:32 +00:00
if page < 1 {
panic(oops.New(nil, "Invalid forum thread page (%d), must be >= 1", page))
}
builder := buildSubforumPath(subforums)
2021-05-11 22:53:23 +00:00
2021-05-05 20:34:32 +00:00
if page > 1 {
builder.WriteRune('/')
builder.WriteString(strconv.Itoa(page))
}
return c.Url(builder.String(), nil)
2021-05-05 20:34:32 +00:00
}
var RegexForumNewThread = regexp.MustCompile(`^/forums(/(?P<subforums>[^\d/]+(/[^\d]+)*))?/t/new$`)
var RegexForumNewThreadSubmit = regexp.MustCompile(`^/forums(/(?P<subforums>[^\d/]+(/[^\d]+)*))?/t/new/submit$`)
2021-05-05 20:34:32 +00:00
func (c *UrlContext) BuildForumNewThread(subforums []string, submit bool) string {
defer CatchPanic()
builder := buildSubforumPath(subforums)
builder.WriteString("/t/new")
if submit {
builder.WriteString("/submit")
}
2021-05-05 20:34:32 +00:00
return c.Url(builder.String(), nil)
2021-05-11 22:53:23 +00:00
}
var RegexForumThread = regexp.MustCompile(`^/forums(/(?P<subforums>[^\d/]+(/[^\d]+)*))?/t/(?P<threadid>\d+)(-([^/]+))?(/(?P<page>\d+))?$`)
2021-05-11 22:53:23 +00:00
func (c *UrlContext) BuildForumThread(subforums []string, threadId int, title string, page int) string {
defer CatchPanic()
2021-05-11 22:53:23 +00:00
builder := buildForumThreadPath(subforums, threadId, title, page)
2021-05-05 20:34:32 +00:00
return c.Url(builder.String(), nil)
2021-05-05 20:34:32 +00:00
}
func (c *UrlContext) BuildForumThreadWithPostHash(subforums []string, threadId int, title string, page int, postId int) string {
defer CatchPanic()
2021-05-11 22:53:23 +00:00
builder := buildForumThreadPath(subforums, threadId, title, page)
2021-11-10 17:34:48 +00:00
return c.UrlWithFragment(builder.String(), nil, strconv.Itoa(postId))
2021-05-11 22:53:23 +00:00
}
var RegexForumPost = regexp.MustCompile(`^/forums(/(?P<subforums>[^\d/]+(/[^\d]+)*))?/t/(?P<threadid>\d+)/p/(?P<postid>\d+)$`)
2021-05-05 20:34:32 +00:00
func (c *UrlContext) BuildForumPost(subforums []string, threadId int, postId int) string {
defer CatchPanic()
2021-05-11 22:53:23 +00:00
builder := buildForumPostPath(subforums, threadId, postId)
2021-05-05 20:34:32 +00:00
return c.Url(builder.String(), nil)
2021-05-05 20:34:32 +00:00
}
var RegexForumPostDelete = regexp.MustCompile(`^/forums(/(?P<subforums>[^\d/]+(/[^\d]+)*))?/t/(?P<threadid>\d+)/p/(?P<postid>\d+)/delete$`)
func (c *UrlContext) BuildForumPostDelete(subforums []string, threadId int, postId int) string {
defer CatchPanic()
2021-05-11 22:53:23 +00:00
builder := buildForumPostPath(subforums, threadId, postId)
builder.WriteString("/delete")
return c.Url(builder.String(), nil)
}
var RegexForumPostEdit = regexp.MustCompile(`^/forums(/(?P<subforums>[^\d/]+(/[^\d]+)*))?/t/(?P<threadid>\d+)/p/(?P<postid>\d+)/edit$`)
func (c *UrlContext) BuildForumPostEdit(subforums []string, threadId int, postId int) string {
defer CatchPanic()
2021-05-11 22:53:23 +00:00
builder := buildForumPostPath(subforums, threadId, postId)
builder.WriteString("/edit")
return c.Url(builder.String(), nil)
}
var RegexForumPostReply = regexp.MustCompile(`^/forums(/(?P<subforums>[^\d/]+(/[^\d]+)*))?/t/(?P<threadid>\d+)/p/(?P<postid>\d+)/reply$`)
func (c *UrlContext) BuildForumPostReply(subforums []string, threadId int, postId int) string {
defer CatchPanic()
2021-05-11 22:53:23 +00:00
builder := buildForumPostPath(subforums, threadId, postId)
builder.WriteString("/reply")
return c.Url(builder.String(), nil)
}
2021-09-09 00:43:24 +00:00
var RegexWikiArticle = regexp.MustCompile(`^/wiki/(?P<threadid>\d+)(-([^/]+))?$`)
2021-05-11 22:53:23 +00:00
/*
* Blog
*/
2021-09-20 15:17:53 +00:00
var RegexBlogsRedirect = regexp.MustCompile(`^/blogs(?P<remainder>.*)`)
2021-05-11 22:53:23 +00:00
var RegexBlog = regexp.MustCompile(`^/blog(/(?P<page>\d+))?$`)
func (c *UrlContext) BuildBlog(page int) string {
defer CatchPanic()
2021-05-11 22:53:23 +00:00
if page < 1 {
panic(oops.New(nil, "Invalid blog page (%d), must be >= 1", page))
}
path := "/blog"
if page > 1 {
path += "/" + strconv.Itoa(page)
}
return c.Url(path, nil)
2021-05-11 22:53:23 +00:00
}
2021-07-30 22:32:19 +00:00
var RegexBlogThread = regexp.MustCompile(`^/blog/p/(?P<threadid>\d+)(-([^/]+))?$`)
2021-05-11 22:53:23 +00:00
func (c *UrlContext) BuildBlogThread(threadId int, title string) string {
defer CatchPanic()
2021-07-30 22:32:19 +00:00
builder := buildBlogThreadPath(threadId, title)
return c.Url(builder.String(), nil)
2021-05-11 22:53:23 +00:00
}
func (c *UrlContext) BuildBlogThreadWithPostHash(threadId int, title string, postId int) string {
defer CatchPanic()
2021-07-30 22:32:19 +00:00
builder := buildBlogThreadPath(threadId, title)
return c.UrlWithFragment(builder.String(), nil, strconv.Itoa(postId))
2021-05-11 22:53:23 +00:00
}
var RegexBlogNewThread = regexp.MustCompile(`^/blog/new$`)
func (c *UrlContext) BuildBlogNewThread() string {
defer CatchPanic()
return c.Url("/blog/new", nil)
}
2021-05-11 22:53:23 +00:00
var RegexBlogPost = regexp.MustCompile(`^/blog/p/(?P<threadid>\d+)/e/(?P<postid>\d+)$`)
func (c *UrlContext) BuildBlogPost(threadId int, postId int) string {
defer CatchPanic()
2021-05-11 22:53:23 +00:00
builder := buildBlogPostPath(threadId, postId)
return c.Url(builder.String(), nil)
2021-05-11 22:53:23 +00:00
}
var RegexBlogPostDelete = regexp.MustCompile(`^/blog/p/(?P<threadid>\d+)/e/(?P<postid>\d+)/delete$`)
func (c *UrlContext) BuildBlogPostDelete(threadId int, postId int) string {
defer CatchPanic()
2021-05-11 22:53:23 +00:00
builder := buildBlogPostPath(threadId, postId)
builder.WriteString("/delete")
return c.Url(builder.String(), nil)
2021-05-11 22:53:23 +00:00
}
var RegexBlogPostEdit = regexp.MustCompile(`^/blog/p/(?P<threadid>\d+)/e/(?P<postid>\d+)/edit$`)
func (c *UrlContext) BuildBlogPostEdit(threadId int, postId int) string {
defer CatchPanic()
2021-05-11 22:53:23 +00:00
builder := buildBlogPostPath(threadId, postId)
builder.WriteString("/edit")
return c.Url(builder.String(), nil)
2021-05-11 22:53:23 +00:00
}
var RegexBlogPostReply = regexp.MustCompile(`^/blog/p/(?P<threadid>\d+)/e/(?P<postid>\d+)/reply$`)
func (c *UrlContext) BuildBlogPostReply(threadId int, postId int) string {
defer CatchPanic()
2021-05-11 22:53:23 +00:00
builder := buildBlogPostPath(threadId, postId)
builder.WriteString("/reply")
return c.Url(builder.String(), nil)
2021-05-11 22:53:23 +00:00
}
/*
* Library
*/
2021-10-23 16:14:10 +00:00
var RegexLibraryAny = regexp.MustCompile(`^/library`)
var RegexLibrary = regexp.MustCompile(`^/library$`)
func BuildLibrary() string {
defer CatchPanic()
return Url("/library", nil)
}
/*
* Episode Guide
*/
var RegexEpisodeList = regexp.MustCompile(`^/episode(/(?P<topic>[^/]+))?$`)
func (c *UrlContext) BuildEpisodeList(topic string) string {
defer CatchPanic()
var builder strings.Builder
builder.WriteString("/episode")
if topic != "" {
builder.WriteString("/")
builder.WriteString(topic)
}
return c.Url(builder.String(), nil)
}
var RegexEpisode = regexp.MustCompile(`^/episode/(?P<topic>[^/]+)/(?P<episode>[^/]+)$`)
func (c *UrlContext) BuildEpisode(topic string, episode string) string {
defer CatchPanic()
return c.Url(fmt.Sprintf("/episode/%s/%s", topic, episode), nil)
}
var RegexCineraIndex = regexp.MustCompile(`^/(?P<topic>[^/]+).index$`)
func (c *UrlContext) BuildCineraIndex(topic string) string {
defer CatchPanic()
return c.Url(fmt.Sprintf("/%s.index", topic), nil)
}
2021-08-16 04:40:56 +00:00
/*
* Discord OAuth
*/
var RegexDiscordOAuthCallback = regexp.MustCompile("^/_discord_callback$")
func BuildDiscordOAuthCallback() string {
return Url("/_discord_callback", nil)
}
2021-08-16 05:07:17 +00:00
var RegexDiscordUnlink = regexp.MustCompile("^/_discord_unlink$")
func BuildDiscordUnlink() string {
return Url("/_discord_unlink", nil)
}
var RegexDiscordShowcaseBacklog = regexp.MustCompile("^/discord_showcase_backlog$")
func BuildDiscordShowcaseBacklog() string {
return Url("/discord_showcase_backlog", nil)
}
2021-11-25 03:59:51 +00:00
/*
* API
*/
var RegexAPICheckUsername = regexp.MustCompile("^/api/check_username$")
func BuildAPICheckUsername() string {
return Url("/api/check_username", nil)
}
2022-03-22 18:07:43 +00:00
/*
* Twitch stuff
*/
var RegexTwitchEventSubCallback = regexp.MustCompile("^/twitch_eventsub$")
func BuildTwitchEventSubCallback() string {
return Url("/twitch_eventsub", nil)
}
var RegexTwitchDebugPage = regexp.MustCompile("^/twitch_debug$")
/*
* User assets
*/
var RegexAssetUpload = regexp.MustCompile("^/upload_asset$")
// NOTE(asaf): Providing the projectSlug avoids any CORS problems.
func (c *UrlContext) BuildAssetUpload() string {
return c.Url("/upload_asset", nil)
}
2021-05-11 22:53:23 +00:00
/*
* Assets
*/
var RegexProjectCSS = regexp.MustCompile("^/assets/project.css$")
2021-05-05 20:34:32 +00:00
func BuildProjectCSS(color string) string {
defer CatchPanic()
2021-05-06 04:04:58 +00:00
return Url("/assets/project.css", []Q{{"color", color}})
2021-05-05 20:34:32 +00:00
}
var RegexMarkdownWorkerJS = regexp.MustCompile("^/assets/markdown_worker.js$")
2021-07-30 22:32:19 +00:00
func BuildMarkdownWorkerJS() string {
2021-07-30 22:32:19 +00:00
defer CatchPanic()
return Url("/assets/markdown_worker.js", nil)
2021-07-30 22:32:19 +00:00
}
2021-09-22 19:18:39 +00:00
var RegexS3Asset *regexp.Regexp
2021-06-22 09:50:40 +00:00
func BuildS3Asset(s3key string) string {
defer CatchPanic()
2021-09-22 19:18:39 +00:00
res := fmt.Sprintf("%s%s", S3BaseUrl, s3key)
return res
2021-06-22 09:50:40 +00:00
}
var RegexPublic = regexp.MustCompile("^/public/.+$")
2021-05-05 20:34:32 +00:00
func BuildPublic(filepath string, cachebust bool) string {
defer CatchPanic()
2021-05-05 20:34:32 +00:00
filepath = strings.Trim(filepath, "/")
if len(strings.TrimSpace(filepath)) == 0 {
panic(oops.New(nil, "Attempted to build a /public url with no path"))
}
if strings.Contains(filepath, "?") {
panic(oops.New(nil, "Public url failpath must not contain query params"))
}
2021-05-05 20:34:32 +00:00
var builder strings.Builder
builder.WriteString("/public")
pathParts := strings.Split(filepath, "/")
for _, part := range pathParts {
part = strings.TrimSpace(part)
if len(part) == 0 {
panic(oops.New(nil, "Attempted to build a /public url with blank path segments: %s", filepath))
}
builder.WriteRune('/')
builder.WriteString(part)
}
var query []Q
if cachebust {
query = []Q{{"v", cacheBustVersion}}
}
return Url(builder.String(), query)
}
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, filepath), cachebust)
}
func BuildUserFile(filepath string) string {
if filepath == "" {
return ""
}
filepath = strings.Trim(filepath, "/")
return BuildPublic(fmt.Sprintf("media/%s", filepath), false)
2021-05-05 20:34:32 +00:00
}
2021-05-11 22:53:23 +00:00
/*
* Other
*/
var RegexForumMarkRead = regexp.MustCompile(`^/markread/(?P<sfid>\d+)$`)
2021-05-11 22:53:23 +00:00
// NOTE(asaf): subforumId == 0 means ALL SUBFORUMS
func (c *UrlContext) BuildForumMarkRead(subforumId int) string {
defer CatchPanic()
if subforumId < 0 {
panic(oops.New(nil, "Invalid subforum ID (%d), must be >= 0", subforumId))
2021-05-11 22:53:23 +00:00
}
var builder strings.Builder
2021-07-23 19:00:37 +00:00
builder.WriteString("/markread/")
builder.WriteString(strconv.Itoa(subforumId))
2021-05-11 22:53:23 +00:00
return c.Url(builder.String(), nil)
2021-05-11 22:53:23 +00:00
}
var RegexCatchAll = regexp.MustCompile("^")
2021-05-11 22:53:23 +00:00
/*
* Helper functions
*/
func buildSubforumPath(subforums []string) *strings.Builder {
2021-05-11 22:53:23 +00:00
for _, subforum := range subforums {
if strings.Contains(subforum, "/") {
panic(oops.New(nil, "Tried building forum url with / in subforum name"))
}
subforum = strings.TrimSpace(subforum)
if len(subforum) == 0 {
panic(oops.New(nil, "Tried building forum url with blank subforum"))
}
}
var builder strings.Builder
builder.WriteString("/forums")
for _, subforum := range subforums {
builder.WriteRune('/')
builder.WriteString(subforum)
}
return &builder
}
func buildForumThreadPath(subforums []string, threadId int, title string, page int) *strings.Builder {
if page < 1 {
panic(oops.New(nil, "Invalid forum thread page (%d), must be >= 1", page))
}
if threadId < 1 {
panic(oops.New(nil, "Invalid forum thread ID (%d), must be >= 1", threadId))
}
builder := buildSubforumPath(subforums)
2021-05-11 22:53:23 +00:00
builder.WriteString("/t/")
builder.WriteString(strconv.Itoa(threadId))
if len(title) > 0 {
builder.WriteRune('-')
builder.WriteString(PathSafeTitle(title))
}
if page > 1 {
builder.WriteRune('/')
builder.WriteString(strconv.Itoa(page))
}
return builder
}
func buildForumPostPath(subforums []string, threadId int, postId int) *strings.Builder {
if threadId < 1 {
panic(oops.New(nil, "Invalid forum thread ID (%d), must be >= 1", threadId))
}
if postId < 1 {
panic(oops.New(nil, "Invalid forum post ID (%d), must be >= 1", postId))
}
builder := buildSubforumPath(subforums)
2021-05-11 22:53:23 +00:00
builder.WriteString("/t/")
builder.WriteString(strconv.Itoa(threadId))
builder.WriteString("/p/")
builder.WriteString(strconv.Itoa(postId))
return builder
}
2021-07-30 22:32:19 +00:00
func buildBlogThreadPath(threadId int, title string) *strings.Builder {
2021-05-11 22:53:23 +00:00
if threadId < 1 {
panic(oops.New(nil, "Invalid blog thread ID (%d), must be >= 1", threadId))
}
var builder strings.Builder
builder.WriteString("/blog/p/")
builder.WriteString(strconv.Itoa(threadId))
if len(title) > 0 {
builder.WriteRune('-')
builder.WriteString(PathSafeTitle(title))
}
return &builder
}
func buildBlogPostPath(threadId int, postId int) *strings.Builder {
if threadId < 1 {
panic(oops.New(nil, "Invalid blog thread ID (%d), must be >= 1", threadId))
}
if postId < 1 {
panic(oops.New(nil, "Invalid blog post ID (%d), must be >= 1", postId))
}
var builder strings.Builder
builder.WriteString("/blog/p/")
builder.WriteString(strconv.Itoa(threadId))
builder.WriteString("/e/")
builder.WriteString(strconv.Itoa(postId))
return &builder
}
func buildLibraryResourcePath(resourceId int) *strings.Builder {
if resourceId < 1 {
panic(oops.New(nil, "Invalid library resource ID (%d), must be >= 1", resourceId))
}
var builder strings.Builder
builder.WriteString("/library/resource/")
builder.WriteString(strconv.Itoa(resourceId))
return &builder
}
func buildLibraryDiscussionPath(resourceId int, threadId int, page int) *strings.Builder {
if page < 1 {
panic(oops.New(nil, "Invalid page number (%d), must be >= 1", page))
}
if threadId < 1 {
panic(oops.New(nil, "Invalid library thread ID (%d), must be >= 1", threadId))
}
builder := buildLibraryResourcePath(resourceId)
builder.WriteString("/d/")
builder.WriteString(strconv.Itoa(threadId))
if page > 1 {
builder.WriteRune('/')
builder.WriteString(strconv.Itoa(page))
}
return builder
}
func buildLibraryPostPath(resourceId int, threadId int, postId int) *strings.Builder {
if threadId < 1 {
panic(oops.New(nil, "Invalid library thread ID (%d), must be >= 1", threadId))
}
if postId < 1 {
panic(oops.New(nil, "Invalid library post ID (%d), must be >= 1", postId))
}
builder := buildLibraryResourcePath(resourceId)
builder.WriteString("/d/")
builder.WriteString(strconv.Itoa(threadId))
builder.WriteString("/p/")
builder.WriteString(strconv.Itoa(postId))
return builder
}
var PathCharsToClear = regexp.MustCompile("[$&`<>{}()\\[\\]\"+#%@;=?\\\\^|~]")
var PathCharsToReplace = regexp.MustCompile("[ :/\\\\]")
func PathSafeTitle(title string) string {
title = strings.ToLower(title)
title = PathCharsToReplace.ReplaceAllLiteralString(title, "_")
title = PathCharsToClear.ReplaceAllLiteralString(title, "")
title = url.PathEscape(title)
return title
}
2021-05-25 13:51:49 +00:00
// TODO(asaf): Find a nicer solution that doesn't require adding a defer to every construction function while also not printing errors in tests.
func CatchPanic() {
if !isTest {
if recovered := recover(); recovered != nil {
logging.LogPanicValue(nil, recovered, "Url construction failed")
}
}
}