2021-05-05 20:34:32 +00:00
package hmnurl
import (
2021-05-25 13:12:20 +00:00
"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
2021-05-25 13:48:57 +00:00
"git.handmade.network/hmn/hmn/src/logging"
2021-05-06 04:04:58 +00:00
"git.handmade.network/hmn/hmn/src/oops"
2021-05-05 20:34:32 +00:00
)
2021-05-16 19:22:45 +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$" )
2021-05-06 05:57:14 +00:00
var RegexHomepage = regexp . MustCompile ( "^/$" )
2021-05-05 20:34:32 +00:00
func BuildHomepage ( ) string {
return Url ( "/" , nil )
}
2021-05-11 22:53:23 +00:00
func BuildProjectHomepage ( projectSlug string ) string {
2021-05-25 13:48:57 +00:00
defer CatchPanic ( )
2021-05-11 22:53:23 +00:00
return ProjectUrl ( "/" , nil , projectSlug )
}
var RegexShowcase = regexp . MustCompile ( "^/showcase$" )
func BuildShowcase ( ) string {
2021-05-25 13:48:57 +00:00
defer CatchPanic ( )
2021-05-11 22:53:23 +00:00
return Url ( "/showcase" , nil )
}
var RegexStreams = regexp . MustCompile ( "^/streams$" )
func BuildStreams ( ) string {
2021-05-25 13:48:57 +00:00
defer CatchPanic ( )
2021-05-11 22:53:23 +00:00
return Url ( "/streams" , nil )
}
var RegexSiteMap = regexp . MustCompile ( "^/sitemap$" )
2021-05-05 20:34:32 +00:00
2021-05-11 22:53:23 +00:00
func BuildSiteMap ( ) string {
2021-05-25 13:48:57 +00:00
defer CatchPanic ( )
2021-05-11 22:53:23 +00:00
return Url ( "/sitemap" , nil )
2021-05-05 20:34:32 +00:00
}
2021-08-17 19:48:44 +00:00
var RegexWhenIsIt = regexp . MustCompile ( "^/whenisit$" )
func BuildWhenIsIt ( ) string {
defer CatchPanic ( )
return Url ( "/whenisit" , nil )
}
2021-05-16 19:22:45 +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 {
2021-05-25 13:48:57 +00:00
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 {
2021-05-25 13:48:57 +00:00
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$" )
2021-06-12 00:48:03 +00:00
func BuildLogoutAction ( redir string ) string {
2021-05-25 13:48:57 +00:00
defer CatchPanic ( )
2021-06-12 00:48:03 +00:00
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 ( ) string {
defer CatchPanic ( )
2021-08-08 20:05:52 +00:00
return Url ( "/register" , nil )
}
var RegexRegistrationSuccess = regexp . MustCompile ( "^/registered_successfully$" )
func BuildRegistrationSuccess ( ) string {
defer CatchPanic ( )
return Url ( "/registered_successfully" , nil )
}
// TODO(asaf): Delete the old version a bit after launch
var RegexOldEmailConfirmation = regexp . MustCompile ( ` ^/_register/confirm/(?P<username>[\w\ \.\,\-@\+\_]+)/(?P<hash>[\d\w]+)/(?P<nonce>.+)[\/]?$ ` )
var RegexEmailConfirmation = regexp . MustCompile ( "^/email_confirmation/(?P<username>[^/]+)/(?P<token>[^/]+)$" )
func BuildEmailConfirmation ( username , token string ) string {
defer CatchPanic ( )
return Url ( fmt . Sprintf ( "/email_confirmation/%s/%s" , url . PathEscape ( username ) , token ) , nil )
}
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
* /
2021-05-06 05:57:14 +00:00
var RegexManifesto = regexp . MustCompile ( "^/manifesto$" )
2021-05-05 20:34:32 +00:00
func BuildManifesto ( ) string {
2021-05-25 13:48:57 +00:00
defer CatchPanic ( )
2021-05-05 20:34:32 +00:00
return Url ( "/manifesto" , nil )
}
2021-05-06 05:57:14 +00:00
var RegexAbout = regexp . MustCompile ( "^/about$" )
2021-05-05 20:34:32 +00:00
func BuildAbout ( ) string {
2021-05-25 13:48:57 +00:00
defer CatchPanic ( )
2021-05-05 20:34:32 +00:00
return Url ( "/about" , nil )
}
2021-05-06 05:57:14 +00:00
var RegexCodeOfConduct = regexp . MustCompile ( "^/code-of-conduct$" )
2021-05-05 20:34:32 +00:00
func BuildCodeOfConduct ( ) string {
2021-05-25 13:48:57 +00:00
defer CatchPanic ( )
2021-05-05 20:34:32 +00:00
return Url ( "/code-of-conduct" , nil )
}
2021-05-06 05:57:14 +00:00
var RegexCommunicationGuidelines = regexp . MustCompile ( "^/communication-guidelines$" )
2021-05-05 20:34:32 +00:00
func BuildCommunicationGuidelines ( ) string {
2021-05-25 13:48:57 +00:00
defer CatchPanic ( )
2021-05-05 20:34:32 +00:00
return Url ( "/communication-guidelines" , nil )
}
2021-05-06 05:57:14 +00:00
var RegexContactPage = regexp . MustCompile ( "^/contact$" )
2021-05-05 20:34:32 +00:00
func BuildContactPage ( ) string {
2021-05-25 13:48:57 +00:00
defer CatchPanic ( )
2021-05-05 20:34:32 +00:00
return Url ( "/contact" , nil )
}
2021-05-06 05:57:14 +00:00
var RegexMonthlyUpdatePolicy = regexp . MustCompile ( "^/monthly-update-policy$" )
2021-05-05 20:34:32 +00:00
func BuildMonthlyUpdatePolicy ( ) string {
2021-05-25 13:48:57 +00:00
defer CatchPanic ( )
2021-05-05 20:34:32 +00:00
return Url ( "/monthly-update-policy" , nil )
}
2021-05-06 05:57:14 +00:00
var RegexProjectSubmissionGuidelines = regexp . MustCompile ( "^/project-guidelines$" )
2021-05-05 20:34:32 +00:00
func BuildProjectSubmissionGuidelines ( ) string {
2021-05-25 13:48:57 +00:00
defer CatchPanic ( )
2021-05-05 20:34:32 +00:00
return Url ( "/project-guidelines" , 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 {
2021-05-25 13:48:57 +00:00
defer CatchPanic ( )
2021-05-11 22:53:23 +00:00
if len ( username ) == 0 {
panic ( oops . New ( nil , "Username must not be blank" ) )
}
2021-08-08 20:05:52 +00:00
return Url ( "/m/" + url . PathEscape ( username ) , nil )
}
2021-08-27 17:58:52 +00:00
var RegexUserSettings = regexp . MustCompile ( ` ^/settings$ ` )
2021-08-17 05:18:04 +00:00
func BuildUserSettings ( section string ) string {
2021-08-27 17:58:52 +00:00
return ProjectUrlWithFragment ( "/settings" , nil , "" , section )
2021-05-11 22:53:23 +00:00
}
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 )
}
2021-05-11 22:53:23 +00:00
/ *
* Feed
* /
2021-05-06 05:57:14 +00:00
var RegexFeed = regexp . MustCompile ( ` ^/feed(/(?P<page>.+)?)?$ ` )
2021-05-05 20:34:32 +00:00
func BuildFeed ( ) string {
2021-05-25 13:48:57 +00:00
defer CatchPanic ( )
2021-05-05 20:34:32 +00:00
return Url ( "/feed" , nil )
}
func BuildFeedWithPage ( page int ) string {
2021-05-25 13:48:57 +00:00
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 )
}
2021-05-31 23:23:04 +00:00
var RegexAtomFeed = regexp . MustCompile ( "^/atom(/(?P<feedtype>[^/]+))?(/new)?$" ) // NOTE(asaf): `/new` for backwards compatibility with old website
2021-05-30 18:35:01 +00:00
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 )
}
}
var RegexProjectNew = regexp . MustCompile ( "^/projects/new$" )
func BuildProjectNew ( ) string {
defer CatchPanic ( )
return Url ( "/projects/new" , nil )
}
var RegexProjectNotApproved = regexp . MustCompile ( "^/p/(?P<slug>.+)$" )
func BuildProjectNotApproved ( slug string ) string {
defer CatchPanic ( )
return Url ( fmt . Sprintf ( "/p/%s" , slug ) , nil )
}
2021-07-08 07:40:30 +00:00
var RegexProjectEdit = regexp . MustCompile ( "^/p/(?P<slug>.+)/edit$" )
func BuildProjectEdit ( slug string , section string ) string {
defer CatchPanic ( )
return ProjectUrlWithFragment ( fmt . Sprintf ( "/p/%s/edit" , slug ) , nil , "" , section )
}
2021-05-11 22:53:23 +00:00
/ *
* Podcast
* /
var RegexPodcast = regexp . MustCompile ( ` ^/podcast$ ` )
func BuildPodcast ( projectSlug string ) string {
2021-05-25 13:48:57 +00:00
defer CatchPanic ( )
2021-05-11 22:53:23 +00:00
return ProjectUrl ( "/podcast" , nil , projectSlug )
}
2021-07-23 03:09:46 +00:00
var RegexPodcastEdit = regexp . MustCompile ( ` ^/podcast/edit$ ` )
func BuildPodcastEdit ( projectSlug string ) string {
defer CatchPanic ( )
return ProjectUrl ( "/podcast/edit" , nil , projectSlug )
}
var RegexPodcastEpisode = regexp . MustCompile ( ` ^/podcast/ep/(?P<episodeid>[^/]+)$ ` )
func BuildPodcastEpisode ( projectSlug string , episodeGUID string ) string {
defer CatchPanic ( )
return ProjectUrl ( fmt . Sprintf ( "/podcast/ep/%s" , episodeGUID ) , nil , projectSlug )
}
var RegexPodcastEpisodeNew = regexp . MustCompile ( ` ^/podcast/ep/new$ ` )
func BuildPodcastEpisodeNew ( projectSlug string ) string {
defer CatchPanic ( )
return ProjectUrl ( "/podcast/ep/new" , nil , projectSlug )
}
var RegexPodcastEpisodeEdit = regexp . MustCompile ( ` ^/podcast/ep/(?P<episodeid>[^/]+)/edit$ ` )
func BuildPodcastEpisodeEdit ( projectSlug string , episodeGUID string ) string {
defer CatchPanic ( )
return ProjectUrl ( fmt . Sprintf ( "/podcast/ep/%s/edit" , episodeGUID ) , nil , projectSlug )
}
var RegexPodcastRSS = regexp . MustCompile ( ` ^/podcast/podcast.xml$ ` )
func BuildPodcastRSS ( projectSlug string ) string {
defer CatchPanic ( )
return ProjectUrl ( "/podcast/podcast.xml" , nil , projectSlug )
}
func BuildPodcastEpisodeFile ( projectSlug string , filename string ) string {
defer CatchPanic ( )
return BuildUserFile ( fmt . Sprintf ( "podcast/%s/%s" , projectSlug , filename ) )
}
2021-05-11 22:53:23 +00:00
/ *
* Forums
* /
2021-07-30 03:40:47 +00:00
// TODO(asaf): This also matches urls generated by BuildForumThread (/t/ is identified as a subforum, and the threadid as a page)
// This shouldn't be a problem since we will match Thread before Subforum in the router, but should we enforce it here?
var RegexForum = regexp . MustCompile ( ` ^/forums(/(?P<subforums>[^\d/]+(/[^\d]+)*))?(/(?P<page>\d+))?$ ` )
2021-05-05 20:34:32 +00:00
2021-07-30 03:40:47 +00:00
func BuildForum ( projectSlug string , subforums [ ] string , page int ) string {
2021-05-25 13:48:57 +00:00
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 ) )
}
2021-07-30 03:40:47 +00:00
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 ProjectUrl ( builder . String ( ) , nil , projectSlug )
}
2021-07-30 03:40:47 +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
2021-06-12 03:51:07 +00:00
func BuildForumNewThread ( projectSlug string , subforums [ ] string , submit bool ) string {
2021-05-25 13:48:57 +00:00
defer CatchPanic ( )
2021-07-30 03:40:47 +00:00
builder := buildSubforumPath ( subforums )
2021-06-12 03:51:07 +00:00
builder . WriteString ( "/t/new" )
if submit {
builder . WriteString ( "/submit" )
}
2021-05-05 20:34:32 +00:00
2021-05-11 22:53:23 +00:00
return ProjectUrl ( builder . String ( ) , nil , projectSlug )
}
2021-07-30 03:40:47 +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 BuildForumThread ( projectSlug string , subforums [ ] string , threadId int , title string , page int ) string {
2021-05-25 13:48:57 +00:00
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 ProjectUrl ( builder . String ( ) , nil , projectSlug )
}
2021-05-11 22:53:23 +00:00
func BuildForumThreadWithPostHash ( projectSlug string , subforums [ ] string , threadId int , title string , page int , postId int ) string {
2021-05-25 13:48:57 +00:00
defer CatchPanic ( )
2021-05-11 22:53:23 +00:00
builder := buildForumThreadPath ( subforums , threadId , title , page )
return ProjectUrlWithFragment ( builder . String ( ) , nil , projectSlug , strconv . Itoa ( postId ) )
}
2021-07-30 03:40:47 +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 BuildForumPost ( projectSlug string , subforums [ ] string , threadId int , postId int ) string {
2021-05-25 13:48:57 +00:00
defer CatchPanic ( )
2021-05-11 22:53:23 +00:00
builder := buildForumPostPath ( subforums , threadId , postId )
2021-05-05 20:34:32 +00:00
return ProjectUrl ( builder . String ( ) , nil , projectSlug )
}
2021-07-30 03:40:47 +00:00
var RegexForumPostDelete = regexp . MustCompile ( ` ^/forums(/(?P<subforums>[^\d/]+(/[^\d]+)*))?/t/(?P<threadid>\d+)/p/(?P<postid>\d+)/delete$ ` )
2021-05-06 05:57:14 +00:00
func BuildForumPostDelete ( projectSlug string , subforums [ ] string , threadId int , postId int ) string {
2021-05-25 13:48:57 +00:00
defer CatchPanic ( )
2021-05-11 22:53:23 +00:00
builder := buildForumPostPath ( subforums , threadId , postId )
builder . WriteString ( "/delete" )
return ProjectUrl ( builder . String ( ) , nil , projectSlug )
2021-05-06 05:57:14 +00:00
}
2021-07-30 03:40:47 +00:00
var RegexForumPostEdit = regexp . MustCompile ( ` ^/forums(/(?P<subforums>[^\d/]+(/[^\d]+)*))?/t/(?P<threadid>\d+)/p/(?P<postid>\d+)/edit$ ` )
2021-05-06 05:57:14 +00:00
func BuildForumPostEdit ( projectSlug string , subforums [ ] string , threadId int , postId int ) string {
2021-05-25 13:48:57 +00:00
defer CatchPanic ( )
2021-05-11 22:53:23 +00:00
builder := buildForumPostPath ( subforums , threadId , postId )
builder . WriteString ( "/edit" )
return ProjectUrl ( builder . String ( ) , nil , projectSlug )
2021-05-06 05:57:14 +00:00
}
2021-07-30 03:40:47 +00:00
var RegexForumPostReply = regexp . MustCompile ( ` ^/forums(/(?P<subforums>[^\d/]+(/[^\d]+)*))?/t/(?P<threadid>\d+)/p/(?P<postid>\d+)/reply$ ` )
2021-05-06 05:57:14 +00:00
// TODO: It's kinda weird that we have "replies" to a specific post. That's not how the data works. I guess this just affects what you see as the "post you're replying to" on the post composer page?
func BuildForumPostReply ( projectSlug string , subforums [ ] string , threadId int , postId int ) string {
2021-05-25 13:48:57 +00:00
defer CatchPanic ( )
2021-05-11 22:53:23 +00:00
builder := buildForumPostPath ( subforums , threadId , postId )
builder . WriteString ( "/reply" )
return ProjectUrl ( builder . String ( ) , nil , projectSlug )
2021-05-06 05:57:14 +00:00
}
2021-05-11 22:53:23 +00:00
/ *
* Blog
* /
var RegexBlogsRedirect = regexp . MustCompile ( ` ^/blogs ` )
var RegexBlog = regexp . MustCompile ( ` ^/blog(/(?P<page>\d+))?$ ` )
func BuildBlog ( projectSlug string , page int ) string {
2021-05-25 13:48:57 +00:00
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 ProjectUrl ( path , nil , projectSlug )
}
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
2021-07-30 22:32:19 +00:00
func BuildBlogThread ( projectSlug string , threadId int , title string ) string {
2021-05-25 13:48:57 +00:00
defer CatchPanic ( )
2021-07-30 22:32:19 +00:00
builder := buildBlogThreadPath ( threadId , title )
2021-05-11 22:53:23 +00:00
return ProjectUrl ( builder . String ( ) , nil , projectSlug )
}
2021-07-30 22:32:19 +00:00
func BuildBlogThreadWithPostHash ( projectSlug string , threadId int , title string , postId int ) string {
2021-05-25 13:48:57 +00:00
defer CatchPanic ( )
2021-07-30 22:32:19 +00:00
builder := buildBlogThreadPath ( threadId , title )
2021-05-11 22:53:23 +00:00
return ProjectUrlWithFragment ( builder . String ( ) , nil , projectSlug , strconv . Itoa ( postId ) )
}
2021-07-30 23:08:42 +00:00
var RegexBlogNewThread = regexp . MustCompile ( ` ^/blog/new$ ` )
func BuildBlogNewThread ( projectSlug string ) string {
defer CatchPanic ( )
return ProjectUrl ( "/blog/new" , nil , projectSlug )
}
2021-05-11 22:53:23 +00:00
var RegexBlogPost = regexp . MustCompile ( ` ^/blog/p/(?P<threadid>\d+)/e/(?P<postid>\d+)$ ` )
func BuildBlogPost ( projectSlug string , threadId int , postId int ) string {
2021-05-25 13:48:57 +00:00
defer CatchPanic ( )
2021-05-11 22:53:23 +00:00
builder := buildBlogPostPath ( threadId , postId )
return ProjectUrl ( builder . String ( ) , nil , projectSlug )
}
var RegexBlogPostDelete = regexp . MustCompile ( ` ^/blog/p/(?P<threadid>\d+)/e/(?P<postid>\d+)/delete$ ` )
func BuildBlogPostDelete ( projectSlug string , threadId int , postId int ) string {
2021-05-25 13:48:57 +00:00
defer CatchPanic ( )
2021-05-11 22:53:23 +00:00
builder := buildBlogPostPath ( threadId , postId )
builder . WriteString ( "/delete" )
return ProjectUrl ( builder . String ( ) , nil , projectSlug )
}
var RegexBlogPostEdit = regexp . MustCompile ( ` ^/blog/p/(?P<threadid>\d+)/e/(?P<postid>\d+)/edit$ ` )
func BuildBlogPostEdit ( projectSlug string , threadId int , postId int ) string {
2021-05-25 13:48:57 +00:00
defer CatchPanic ( )
2021-05-11 22:53:23 +00:00
builder := buildBlogPostPath ( threadId , postId )
builder . WriteString ( "/edit" )
return ProjectUrl ( builder . String ( ) , nil , projectSlug )
}
var RegexBlogPostReply = regexp . MustCompile ( ` ^/blog/p/(?P<threadid>\d+)/e/(?P<postid>\d+)/reply$ ` )
func BuildBlogPostReply ( projectSlug string , threadId int , postId int ) string {
2021-05-25 13:48:57 +00:00
defer CatchPanic ( )
2021-05-11 22:53:23 +00:00
builder := buildBlogPostPath ( threadId , postId )
builder . WriteString ( "/reply" )
return ProjectUrl ( builder . String ( ) , nil , projectSlug )
}
/ *
* Library
* /
var RegexLibrary = regexp . MustCompile ( ` ^/library$ ` )
func BuildLibrary ( projectSlug string ) string {
2021-05-25 13:48:57 +00:00
defer CatchPanic ( )
2021-05-11 22:53:23 +00:00
return ProjectUrl ( "/library" , nil , projectSlug )
}
var RegexLibraryAll = regexp . MustCompile ( ` ^/library/all$ ` )
func BuildLibraryAll ( projectSlug string ) string {
2021-05-25 13:48:57 +00:00
defer CatchPanic ( )
2021-05-11 22:53:23 +00:00
return ProjectUrl ( "/library/all" , nil , projectSlug )
}
var RegexLibraryTopic = regexp . MustCompile ( ` ^/library/topic/(?P<topicid>\d+)$ ` )
func BuildLibraryTopic ( projectSlug string , topicId int ) string {
2021-05-25 13:48:57 +00:00
defer CatchPanic ( )
2021-05-11 22:53:23 +00:00
if topicId < 1 {
panic ( oops . New ( nil , "Invalid library topic ID (%d), must be >= 1" , topicId ) )
}
var builder strings . Builder
builder . WriteString ( "/library/topic/" )
builder . WriteString ( strconv . Itoa ( topicId ) )
return ProjectUrl ( builder . String ( ) , nil , projectSlug )
}
var RegexLibraryResource = regexp . MustCompile ( ` ^/library/resource/(?P<resourceid>\d+)$ ` )
func BuildLibraryResource ( projectSlug string , resourceId int ) string {
2021-05-25 13:48:57 +00:00
defer CatchPanic ( )
2021-05-11 22:53:23 +00:00
builder := buildLibraryResourcePath ( resourceId )
return ProjectUrl ( builder . String ( ) , nil , projectSlug )
}
2021-08-28 10:40:13 +00:00
/ *
* Episode Guide
* /
var RegexEpisodeList = regexp . MustCompile ( ` ^/episode(/(?P<topic>[^/]+))?$ ` )
func BuildEpisodeList ( projectSlug string , topic string ) string {
defer CatchPanic ( )
var builder strings . Builder
builder . WriteString ( "/episode" )
if topic != "" {
builder . WriteString ( "/" )
builder . WriteString ( topic )
}
return ProjectUrl ( builder . String ( ) , nil , projectSlug )
}
var RegexEpisode = regexp . MustCompile ( ` ^/episode/(?P<topic>[^/]+)/(?P<episode>[^/]+)$ ` )
func BuildEpisode ( projectSlug string , topic string , episode string ) string {
defer CatchPanic ( )
return ProjectUrl ( fmt . Sprintf ( "/episode/%s/%s" , topic , episode ) , nil , projectSlug )
}
var RegexCineraIndex = regexp . MustCompile ( ` ^/(?P<topic>[^/]+).index$ ` )
func BuildCineraIndex ( projectSlug string , topic string ) string {
defer CatchPanic ( )
return ProjectUrl ( fmt . Sprintf ( "/%s.index" , topic ) , nil , projectSlug )
}
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 )
}
2021-08-27 17:58:52 +00:00
var RegexDiscordShowcaseBacklog = regexp . MustCompile ( "^/discord_showcase_backlog$" )
func BuildDiscordShowcaseBacklog ( ) string {
return Url ( "/discord_showcase_backlog" , nil )
}
2021-05-11 22:53:23 +00:00
/ *
* Assets
* /
2021-05-06 05:57:14 +00:00
var RegexProjectCSS = regexp . MustCompile ( "^/assets/project.css$" )
2021-05-05 20:34:32 +00:00
func BuildProjectCSS ( color string ) string {
2021-05-25 13:48:57 +00:00
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
}
2021-07-30 22:32:19 +00:00
var RegexEditorPreviewsJS = regexp . MustCompile ( "^/assets/editorpreviews.js$" )
func BuildEditorPreviewsJS ( ) string {
defer CatchPanic ( )
return Url ( "/assets/editorpreviews.js" , nil )
}
2021-06-22 09:50:40 +00:00
// NOTE(asaf): No Regex or tests for remote assets, since we don't parse it ourselves
func BuildS3Asset ( s3key string ) string {
defer CatchPanic ( )
return fmt . Sprintf ( "%s%s" , S3BaseUrl , s3key )
}
2021-05-06 05:57:14 +00:00
var RegexPublic = regexp . MustCompile ( "^/public/.+$" )
2021-05-05 20:34:32 +00:00
2021-05-25 13:12:20 +00:00
func BuildPublic ( filepath string , cachebust bool ) string {
2021-05-25 13:48:57 +00:00
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" ) )
}
2021-05-25 13:12:20 +00:00
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 )
}
2021-05-25 13:12:20 +00:00
var query [ ] Q
if cachebust {
query = [ ] Q { { "v" , cacheBust } }
}
return Url ( builder . String ( ) , query )
}
func BuildTheme ( filepath string , theme string , cachebust bool ) string {
2021-05-25 13:48:57 +00:00
defer CatchPanic ( )
2021-05-31 23:23:04 +00:00
filepath = strings . Trim ( filepath , "/" )
2021-05-25 13:12:20 +00:00
if len ( theme ) == 0 {
panic ( oops . New ( nil , "Theme can't be blank" ) )
}
2021-05-31 23:23:04 +00:00
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 )
2021-05-05 20:34:32 +00:00
}
2021-05-11 22:53:23 +00:00
/ *
* Other
* /
2021-07-30 03:40:47 +00:00
var RegexForumMarkRead = regexp . MustCompile ( ` ^/markread/(?P<sfid>\d+)$ ` )
2021-05-11 22:53:23 +00:00
2021-07-30 03:40:47 +00:00
// NOTE(asaf): subforumId == 0 means ALL SUBFORUMS
func BuildForumMarkRead ( subforumId int ) string {
2021-05-25 13:48:57 +00:00
defer CatchPanic ( )
2021-07-30 03:40:47 +00:00
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/" )
2021-07-30 03:40:47 +00:00
builder . WriteString ( strconv . Itoa ( subforumId ) )
2021-05-11 22:53:23 +00:00
return Url ( builder . String ( ) , nil )
}
2021-05-06 05:57:14 +00:00
var RegexCatchAll = regexp . MustCompile ( "" )
2021-05-11 22:53:23 +00:00
/ *
* Helper functions
* /
2021-07-30 03:40:47 +00:00
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 ) )
}
2021-07-30 03:40:47 +00:00
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 ) )
}
2021-07-30 03:40:47 +00:00
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:48:57 +00:00
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.
2021-05-25 13:48:57 +00:00
func CatchPanic ( ) {
if ! isTest {
if recovered := recover ( ) ; recovered != nil {
logging . LogPanicValue ( nil , recovered , "Url construction failed" )
}
}
}