Add route grouping stuff for projects (needs thorough testing)
This commit is contained in:
parent
7486f9e57d
commit
ff901e4fb8
|
@ -9,8 +9,8 @@ set -euxo pipefail
|
|||
# TODO(opensource): We should adapt Asaf's seedfile command and then delete this.
|
||||
|
||||
THIS_PATH=$(pwd)
|
||||
BETA_PATH='/mnt/c/Users/bvisn/Developer/handmade/handmade-beta'
|
||||
# BETA_PATH='/Users/benvisness/Developer/handmade/handmade-beta'
|
||||
#BETA_PATH='/mnt/c/Users/bvisn/Developer/handmade/handmade-beta'
|
||||
BETA_PATH='/Users/benvisness/Developer/handmade/handmade-beta'
|
||||
|
||||
pushd $BETA_PATH
|
||||
docker-compose down -v
|
||||
|
@ -19,4 +19,5 @@ pushd $BETA_PATH
|
|||
|
||||
docker-compose exec postgres bash -c "psql -U postgres -c \"CREATE ROLE hmn CREATEDB LOGIN PASSWORD 'password';\""
|
||||
popd
|
||||
go run src/main.go seedfile local/backups/hmn_pg_dump_live_2021-09-06
|
||||
#go run src/main.go seedfile local/backups/hmn_pg_dump_live_2021-09-06
|
||||
go run src/main.go seedfile local/backups/hmn_pg_dump_live_2021-10-23
|
||||
|
|
|
@ -97,8 +97,6 @@ func BuildRegistrationSuccess() string {
|
|||
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 {
|
||||
|
@ -295,14 +293,14 @@ func BuildProjectNew() string {
|
|||
return Url("/projects/new", nil)
|
||||
}
|
||||
|
||||
var RegexPersonalProjectHomepage = regexp.MustCompile("^/p/(?P<id>[0-9]+)(/(?P<slug>[^/]*))?")
|
||||
var RegexPersonalProject = regexp.MustCompile("^/p/(?P<projectid>[0-9]+)(/(?P<slug>[a-zA-Z0-9-]+))?")
|
||||
|
||||
func BuildPersonalProjectHomepage(id int, slug string) string {
|
||||
func BuildPersonalProject(id int, slug string) string {
|
||||
defer CatchPanic()
|
||||
return Url(fmt.Sprintf("/p/%d/%s", id, slug), nil)
|
||||
}
|
||||
|
||||
var RegexProjectEdit = regexp.MustCompile("^/p/(?P<slug>.+)/edit$")
|
||||
var RegexProjectEdit = regexp.MustCompile("^/edit$")
|
||||
|
||||
func BuildProjectEdit(slug string, section string) string {
|
||||
defer CatchPanic()
|
||||
|
@ -730,7 +728,7 @@ func BuildForumMarkRead(projectSlug string, subforumId int) string {
|
|||
return ProjectUrl(builder.String(), nil, projectSlug)
|
||||
}
|
||||
|
||||
var RegexCatchAll = regexp.MustCompile("")
|
||||
var RegexCatchAll = regexp.MustCompile("^")
|
||||
|
||||
/*
|
||||
* Helper functions
|
||||
|
|
|
@ -62,7 +62,7 @@ var LifecycleBadgeStrings = map[models.ProjectLifecycle]string{
|
|||
func ProjectUrl(p *models.Project) string {
|
||||
var url string
|
||||
if p.Personal {
|
||||
url = hmnurl.BuildPersonalProjectHomepage(p.ID, models.GeneratePersonalProjectSlug(p.Name))
|
||||
url = hmnurl.BuildPersonalProject(p.ID, models.GeneratePersonalProjectSlug(p.Name))
|
||||
} else {
|
||||
url = hmnurl.BuildOfficialProjectHomepage(p.Slug)
|
||||
}
|
||||
|
|
|
@ -26,7 +26,7 @@ func getBaseData(c *RequestContext, title string, breadcrumbs []templates.Breadc
|
|||
notices := getNoticesFromCookie(c)
|
||||
|
||||
if len(breadcrumbs) > 0 {
|
||||
projectUrl := hmnurl.BuildProjectHomepage(c.CurrentProject.Slug)
|
||||
projectUrl := UrlForProject(c.CurrentProject)
|
||||
if breadcrumbs[0].Url != projectUrl {
|
||||
rootBreadcrumb := templates.Breadcrumb{
|
||||
Name: c.CurrentProject.Name,
|
||||
|
@ -42,7 +42,7 @@ func getBaseData(c *RequestContext, title string, breadcrumbs []templates.Breadc
|
|||
Breadcrumbs: breadcrumbs,
|
||||
|
||||
CurrentUrl: c.FullUrl(),
|
||||
CurrentProjectUrl: hmnurl.BuildProjectHomepage(c.CurrentProject.Slug),
|
||||
CurrentProjectUrl: UrlForProject(c.CurrentProject),
|
||||
LoginPageUrl: hmnurl.BuildLoginPage(c.FullUrl()),
|
||||
ProjectCSSUrl: hmnurl.BuildProjectCSS(c.CurrentProject.Color1),
|
||||
|
||||
|
|
|
@ -517,7 +517,7 @@ func BlogPostDeleteSubmit(c *RequestContext) ResponseData {
|
|||
}
|
||||
|
||||
if threadDeleted {
|
||||
projectUrl := hmnurl.BuildProjectHomepage(c.CurrentProject.Slug)
|
||||
projectUrl := UrlForProject(c.CurrentProject)
|
||||
return c.Redirect(projectUrl, http.StatusSeeOther)
|
||||
} else {
|
||||
thread, err := FetchThread(c.Context(), c.Conn, c.CurrentUser, cd.ThreadID, ThreadsQuery{
|
||||
|
|
|
@ -9,7 +9,7 @@ import (
|
|||
func ProjectBreadcrumb(project *models.Project) templates.Breadcrumb {
|
||||
return templates.Breadcrumb{
|
||||
Name: project.Name,
|
||||
Url: hmnurl.BuildProjectHomepage(project.Slug),
|
||||
Url: UrlForProject(project),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -53,7 +53,7 @@ func EpisodeList(c *RequestContext) ResponseData {
|
|||
defaultTopic, hasEpisodeGuide := config.Config.EpisodeGuide.Projects[slug]
|
||||
|
||||
if !hasEpisodeGuide {
|
||||
return c.Redirect(hmnurl.BuildProjectHomepage(slug), http.StatusSeeOther)
|
||||
return c.Redirect(UrlForProject(c.CurrentProject), http.StatusSeeOther)
|
||||
}
|
||||
|
||||
if topic == "" {
|
||||
|
@ -114,7 +114,7 @@ func Episode(c *RequestContext) ResponseData {
|
|||
_, hasEpisodeGuide := config.Config.EpisodeGuide.Projects[slug]
|
||||
|
||||
if !hasEpisodeGuide {
|
||||
return c.Redirect(hmnurl.BuildProjectHomepage(slug), http.StatusSeeOther)
|
||||
return c.Redirect(UrlForProject(c.CurrentProject), http.StatusSeeOther)
|
||||
}
|
||||
|
||||
_, foundTopic := topicsForProject(slug, topic)
|
||||
|
|
|
@ -15,7 +15,7 @@ func UrlForGenericThread(thread *models.Thread, lineageBuilder *models.SubforumL
|
|||
return hmnurl.BuildForumThread(projectSlug, lineageBuilder.GetSubforumLineageSlugs(*thread.SubforumID), thread.ID, thread.Title, 1)
|
||||
}
|
||||
|
||||
return hmnurl.BuildProjectHomepage(projectSlug)
|
||||
return hmnurl.BuildOfficialProjectHomepage(projectSlug) // TODO: both official and personal projects
|
||||
}
|
||||
|
||||
func UrlForGenericPost(thread *models.Thread, post *models.Post, lineageBuilder *models.SubforumLineageBuilder, projectSlug string) string {
|
||||
|
@ -26,7 +26,7 @@ func UrlForGenericPost(thread *models.Thread, post *models.Post, lineageBuilder
|
|||
return hmnurl.BuildForumPost(projectSlug, lineageBuilder.GetSubforumLineageSlugs(*thread.SubforumID), post.ThreadID, post.ID)
|
||||
}
|
||||
|
||||
return hmnurl.BuildProjectHomepage(projectSlug)
|
||||
return hmnurl.BuildOfficialProjectHomepage(projectSlug) // TODO: both official and personal projects
|
||||
}
|
||||
|
||||
var PostTypeMap = map[models.ThreadType][]templates.PostType{
|
||||
|
@ -55,7 +55,7 @@ func GenericThreadBreadcrumbs(lineageBuilder *models.SubforumLineageBuilder, pro
|
|||
result = []templates.Breadcrumb{
|
||||
{
|
||||
Name: project.Name,
|
||||
Url: hmnurl.BuildProjectHomepage(project.Slug),
|
||||
Url: UrlForProject(project),
|
||||
},
|
||||
{
|
||||
Name: ThreadTypeDisplayNames[thread.Type],
|
||||
|
@ -73,7 +73,7 @@ func BuildProjectRootResourceUrl(projectSlug string, kind models.ThreadType) str
|
|||
case models.ThreadTypeForumPost:
|
||||
return hmnurl.BuildForum(projectSlug, nil, 1)
|
||||
}
|
||||
return hmnurl.BuildProjectHomepage(projectSlug)
|
||||
return hmnurl.BuildOfficialProjectHomepage(projectSlug) // TODO: both official and personal projects
|
||||
}
|
||||
|
||||
func MakePostListItem(
|
||||
|
|
|
@ -20,6 +20,7 @@ type ProjectsQuery struct {
|
|||
// Available on all project queries
|
||||
Lifecycles []models.ProjectLifecycle // if empty, defaults to models.VisibleProjectLifecycles
|
||||
Types ProjectTypeQuery // bitfield
|
||||
IncludeHidden bool
|
||||
|
||||
// Ignored when using FetchProject
|
||||
ProjectIDs []int // if empty, all projects
|
||||
|
@ -62,8 +63,11 @@ func FetchProjects(
|
|||
FROM
|
||||
handmade_project AS project
|
||||
WHERE
|
||||
NOT hidden
|
||||
TRUE
|
||||
`)
|
||||
if !q.IncludeHidden {
|
||||
qb.Add(`AND NOT hidden`)
|
||||
}
|
||||
if len(q.ProjectIDs) > 0 {
|
||||
qb.Add(`AND project.id = ANY ($?)`, q.ProjectIDs)
|
||||
}
|
||||
|
@ -386,7 +390,7 @@ func FetchProjectOwners(
|
|||
|
||||
func UrlForProject(p *models.Project) string {
|
||||
if p.Personal {
|
||||
return hmnurl.BuildPersonalProjectHomepage(p.ID, models.GeneratePersonalProjectSlug(p.Name))
|
||||
return hmnurl.BuildPersonalProject(p.ID, models.GeneratePersonalProjectSlug(p.Name))
|
||||
} else {
|
||||
return hmnurl.BuildOfficialProjectHomepage(p.Slug)
|
||||
}
|
||||
|
|
|
@ -1,12 +1,10 @@
|
|||
package website
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"math"
|
||||
"math/rand"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"git.handmade.network/hmn/hmn/src/db"
|
||||
|
@ -155,49 +153,15 @@ type ProjectHomepageData struct {
|
|||
|
||||
func ProjectHomepage(c *RequestContext) ResponseData {
|
||||
maxRecentActivity := 15
|
||||
var project *models.Project
|
||||
|
||||
if c.CurrentProject.IsHMN() {
|
||||
// Viewing a personal project
|
||||
idStr := c.PathParams["id"]
|
||||
slug := c.PathParams["slug"]
|
||||
|
||||
id, err := strconv.Atoi(idStr)
|
||||
if err != nil {
|
||||
panic(oops.New(err, "id was not numeric (bad regex in routing)"))
|
||||
}
|
||||
|
||||
if id == models.HMNProjectID {
|
||||
return c.Redirect(hmnurl.BuildHomepage(), http.StatusPermanentRedirect)
|
||||
}
|
||||
|
||||
p, err := FetchProject(c.Context(), c.Conn, c.CurrentUser, id, ProjectsQuery{})
|
||||
if err != nil {
|
||||
if errors.Is(err, db.NotFound) {
|
||||
return FourOhFour(c)
|
||||
} else {
|
||||
return c.ErrorResponse(http.StatusInternalServerError, oops.New(err, "failed to fetch project by slug"))
|
||||
}
|
||||
}
|
||||
|
||||
correctSlug := models.GeneratePersonalProjectSlug(p.Project.Name)
|
||||
if slug != correctSlug {
|
||||
return c.Redirect(hmnurl.BuildPersonalProjectHomepage(id, correctSlug), http.StatusPermanentRedirect)
|
||||
}
|
||||
|
||||
project = &p.Project
|
||||
} else {
|
||||
project = c.CurrentProject
|
||||
}
|
||||
|
||||
if project == nil {
|
||||
if c.CurrentProject == nil {
|
||||
return FourOhFour(c)
|
||||
}
|
||||
|
||||
// There are no further permission checks to do, because permissions are
|
||||
// checked whatever way we fetch the project.
|
||||
|
||||
owners, err := FetchProjectOwners(c.Context(), c.Conn, project.ID)
|
||||
owners, err := FetchProjectOwners(c.Context(), c.Conn, c.CurrentProject.ID)
|
||||
if err != nil {
|
||||
return c.ErrorResponse(http.StatusInternalServerError, err)
|
||||
}
|
||||
|
@ -215,7 +179,7 @@ func ProjectHomepage(c *RequestContext) ResponseData {
|
|||
WHERE
|
||||
handmade_project_screenshots.project_id = $1
|
||||
`,
|
||||
project.ID,
|
||||
c.CurrentProject.ID,
|
||||
)
|
||||
if err != nil {
|
||||
return c.ErrorResponse(http.StatusInternalServerError, oops.New(err, "failed to fetch screenshots for project"))
|
||||
|
@ -235,7 +199,7 @@ func ProjectHomepage(c *RequestContext) ResponseData {
|
|||
link.project_id = $1
|
||||
ORDER BY link.ordering ASC
|
||||
`,
|
||||
project.ID,
|
||||
c.CurrentProject.ID,
|
||||
)
|
||||
if err != nil {
|
||||
return c.ErrorResponse(http.StatusInternalServerError, oops.New(err, "failed to fetch project links"))
|
||||
|
@ -265,7 +229,7 @@ func ProjectHomepage(c *RequestContext) ResponseData {
|
|||
ORDER BY post.postdate DESC
|
||||
LIMIT $2
|
||||
`,
|
||||
project.ID,
|
||||
c.CurrentProject.ID,
|
||||
maxRecentActivity,
|
||||
)
|
||||
if err != nil {
|
||||
|
@ -275,36 +239,36 @@ func ProjectHomepage(c *RequestContext) ResponseData {
|
|||
|
||||
var projectHomepageData ProjectHomepageData
|
||||
|
||||
projectHomepageData.BaseData = getBaseData(c, project.Name, nil)
|
||||
if canEdit {
|
||||
// TODO: Move to project-specific navigation
|
||||
// projectHomepageData.BaseData.Header.EditURL = hmnurl.BuildProjectEdit(project.Slug, "")
|
||||
}
|
||||
projectHomepageData.BaseData = getBaseData(c, c.CurrentProject.Name, nil)
|
||||
//if canEdit {
|
||||
// // TODO: Move to project-specific navigation
|
||||
// // projectHomepageData.BaseData.Header.EditURL = hmnurl.BuildProjectEdit(project.Slug, "")
|
||||
//}
|
||||
projectHomepageData.BaseData.OpenGraphItems = append(projectHomepageData.BaseData.OpenGraphItems, templates.OpenGraphItem{
|
||||
Property: "og:description",
|
||||
Value: project.Blurb,
|
||||
Value: c.CurrentProject.Blurb,
|
||||
})
|
||||
|
||||
projectHomepageData.Project = templates.ProjectToTemplate(project, c.Theme)
|
||||
projectHomepageData.Project = templates.ProjectToTemplate(c.CurrentProject, c.Theme)
|
||||
for _, owner := range owners {
|
||||
projectHomepageData.Owners = append(projectHomepageData.Owners, templates.UserToTemplate(owner, c.Theme))
|
||||
}
|
||||
|
||||
if project.Hidden {
|
||||
if c.CurrentProject.Hidden {
|
||||
projectHomepageData.BaseData.AddImmediateNotice(
|
||||
"hidden",
|
||||
"NOTICE: This project is hidden. It is currently visible only to owners and site admins.",
|
||||
)
|
||||
}
|
||||
|
||||
if project.Lifecycle != models.ProjectLifecycleActive {
|
||||
switch project.Lifecycle {
|
||||
if c.CurrentProject.Lifecycle != models.ProjectLifecycleActive {
|
||||
switch c.CurrentProject.Lifecycle {
|
||||
case models.ProjectLifecycleUnapproved:
|
||||
projectHomepageData.BaseData.AddImmediateNotice(
|
||||
"unapproved",
|
||||
fmt.Sprintf(
|
||||
"NOTICE: This project has not yet been submitted for approval. It is only visible to owners. Please <a href=\"%s\">submit it for approval</a> when the project content is ready for review.",
|
||||
hmnurl.BuildProjectEdit(project.Slug, "submit"),
|
||||
hmnurl.BuildProjectEdit(c.CurrentProject.Slug, "submit"),
|
||||
),
|
||||
)
|
||||
case models.ProjectLifecycleApprovalRequired:
|
||||
|
@ -348,7 +312,7 @@ func ProjectHomepage(c *RequestContext) ResponseData {
|
|||
lineageBuilder,
|
||||
&post.(*postQuery).Post,
|
||||
&post.(*postQuery).Thread,
|
||||
project,
|
||||
c.CurrentProject,
|
||||
&post.(*postQuery).Author,
|
||||
c.Theme,
|
||||
))
|
||||
|
|
|
@ -30,12 +30,13 @@ type Router struct {
|
|||
|
||||
type Route struct {
|
||||
Method string
|
||||
Regex *regexp.Regexp
|
||||
Regexes []*regexp.Regexp
|
||||
Handler Handler
|
||||
}
|
||||
|
||||
type RouteBuilder struct {
|
||||
Router *Router
|
||||
Prefixes []*regexp.Regexp
|
||||
Middleware Middleware
|
||||
}
|
||||
|
||||
|
@ -44,11 +45,17 @@ type Handler func(c *RequestContext) ResponseData
|
|||
type Middleware func(h Handler) Handler
|
||||
|
||||
func (rb *RouteBuilder) Handle(methods []string, regex *regexp.Regexp, h Handler) {
|
||||
// Ensure that this regex matches the start of the string
|
||||
regexStr := regex.String()
|
||||
if len(regexStr) == 0 || regexStr[0] != '^' {
|
||||
panic("All routing regexes must begin with '^'")
|
||||
}
|
||||
|
||||
h = rb.Middleware(h)
|
||||
for _, method := range methods {
|
||||
rb.Router.Routes = append(rb.Router.Routes, Route{
|
||||
Method: method,
|
||||
Regex: regex,
|
||||
Regexes: append(rb.Prefixes, regex),
|
||||
Handler: h,
|
||||
})
|
||||
}
|
||||
|
@ -66,33 +73,36 @@ func (rb *RouteBuilder) POST(regex *regexp.Regexp, h Handler) {
|
|||
rb.Handle([]string{http.MethodPost}, regex, h)
|
||||
}
|
||||
|
||||
func (rb *RouteBuilder) Group(regex *regexp.Regexp, addRoutes func(rb *RouteBuilder)) {
|
||||
newRb := *rb
|
||||
newRb.Prefixes = append(newRb.Prefixes, regex)
|
||||
addRoutes(&newRb)
|
||||
}
|
||||
|
||||
func (r *Router) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
|
||||
path := req.URL.Path
|
||||
nextroute:
|
||||
for _, route := range r.Routes {
|
||||
if route.Method != "" && req.Method != route.Method {
|
||||
continue
|
||||
}
|
||||
|
||||
path = strings.TrimSuffix(path, "/")
|
||||
if path == "" {
|
||||
path = "/"
|
||||
currentPath := strings.TrimSuffix(req.URL.Path, "/")
|
||||
if currentPath == "" {
|
||||
currentPath = "/"
|
||||
}
|
||||
|
||||
match := route.Regex.FindStringSubmatch(path)
|
||||
if match == nil {
|
||||
continue
|
||||
var params map[string]string
|
||||
for _, regex := range route.Regexes {
|
||||
|
||||
match := regex.FindStringSubmatch(currentPath)
|
||||
if len(match) == 0 {
|
||||
continue nextroute
|
||||
}
|
||||
|
||||
c := &RequestContext{
|
||||
Route: route.Regex.String(),
|
||||
Logger: logging.GlobalLogger(),
|
||||
Req: req,
|
||||
Res: rw,
|
||||
if params == nil {
|
||||
params = map[string]string{}
|
||||
}
|
||||
|
||||
if len(match) > 0 {
|
||||
params := map[string]string{}
|
||||
subexpNames := route.Regex.SubexpNames()
|
||||
subexpNames := regex.SubexpNames()
|
||||
for i, paramValue := range match {
|
||||
paramName := subexpNames[i]
|
||||
if paramName == "" {
|
||||
|
@ -100,15 +110,35 @@ func (r *Router) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
|
|||
}
|
||||
params[paramName] = paramValue
|
||||
}
|
||||
c.PathParams = params
|
||||
|
||||
// Make sure that we never consume trailing slashes even if the route regex matches them
|
||||
toConsume := strings.TrimSuffix(match[0], "/")
|
||||
currentPath = currentPath[len(toConsume):]
|
||||
if currentPath == "" {
|
||||
currentPath = "/"
|
||||
}
|
||||
}
|
||||
|
||||
var routeStrings []string
|
||||
for _, regex := range route.Regexes {
|
||||
routeStrings = append(routeStrings, regex.String())
|
||||
}
|
||||
|
||||
c := &RequestContext{
|
||||
Route: fmt.Sprintf("%v", routeStrings),
|
||||
Logger: logging.GlobalLogger(),
|
||||
Req: req,
|
||||
Res: rw,
|
||||
PathParams: params,
|
||||
}
|
||||
c.PathParams = params
|
||||
|
||||
doRequest(rw, c, route.Handler)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
panic(fmt.Sprintf("Path '%s' did not match any routes! Make sure to register a wildcard route to act as a 404.", path))
|
||||
panic(fmt.Sprintf("Path '%s' did not match any routes! Make sure to register a wildcard route to act as a 404.", req.URL))
|
||||
}
|
||||
|
||||
type RequestContext struct {
|
||||
|
|
|
@ -8,6 +8,7 @@ import (
|
|||
"math/rand"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
|
@ -154,14 +155,6 @@ func NewWebsiteRoutes(longRequestContext context.Context, conn *pgxpool.Pool, pe
|
|||
return res
|
||||
})
|
||||
|
||||
anyProject.GET(hmnurl.RegexHomepage, func(c *RequestContext) ResponseData {
|
||||
if c.CurrentProject.IsHMN() {
|
||||
return Index(c)
|
||||
} else {
|
||||
return ProjectHomepage(c)
|
||||
}
|
||||
})
|
||||
|
||||
// NOTE(asaf): HMN-only routes:
|
||||
hmnOnly.GET(hmnurl.RegexManifesto, Manifesto)
|
||||
hmnOnly.GET(hmnurl.RegexAbout, About)
|
||||
|
@ -175,14 +168,13 @@ func NewWebsiteRoutes(longRequestContext context.Context, conn *pgxpool.Pool, pe
|
|||
|
||||
hmnOnly.GET(hmnurl.RegexOldHome, Index)
|
||||
|
||||
hmnOnly.POST(hmnurl.RegexLoginAction, securityTimerMiddleware(time.Millisecond*100, Login)) // TODO(asaf): Adjust this after launch
|
||||
hmnOnly.POST(hmnurl.RegexLoginAction, securityTimerMiddleware(time.Millisecond*100, Login))
|
||||
hmnOnly.GET(hmnurl.RegexLogoutAction, Logout)
|
||||
hmnOnly.GET(hmnurl.RegexLoginPage, LoginPage)
|
||||
|
||||
hmnOnly.GET(hmnurl.RegexRegister, RegisterNewUser)
|
||||
hmnOnly.POST(hmnurl.RegexRegister, securityTimerMiddleware(email.ExpectedEmailSendDuration, RegisterNewUserSubmit))
|
||||
hmnOnly.GET(hmnurl.RegexRegistrationSuccess, RegisterNewUserSuccess)
|
||||
hmnOnly.GET(hmnurl.RegexOldEmailConfirmation, EmailConfirmation) // TODO(asaf): Delete this a bit after launch
|
||||
hmnOnly.GET(hmnurl.RegexEmailConfirmation, EmailConfirmation)
|
||||
hmnOnly.POST(hmnurl.RegexEmailConfirmation, EmailConfirmationSubmit)
|
||||
|
||||
|
@ -202,7 +194,6 @@ func NewWebsiteRoutes(longRequestContext context.Context, conn *pgxpool.Pool, pe
|
|||
hmnOnly.GET(hmnurl.RegexShowcase, Showcase)
|
||||
hmnOnly.GET(hmnurl.RegexSnippet, Snippet)
|
||||
hmnOnly.GET(hmnurl.RegexProjectIndex, ProjectIndex)
|
||||
hmnOnly.GET(hmnurl.RegexPersonalProjectHomepage, ProjectHomepage)
|
||||
|
||||
hmnOnly.GET(hmnurl.RegexDiscordOAuthCallback, authMiddleware(DiscordOAuthCallback))
|
||||
hmnOnly.POST(hmnurl.RegexDiscordUnlink, authMiddleware(csrfMiddleware(DiscordUnlink)))
|
||||
|
@ -225,37 +216,98 @@ func NewWebsiteRoutes(longRequestContext context.Context, conn *pgxpool.Pool, pe
|
|||
hmnOnly.GET(hmnurl.RegexLibraryAny, LibraryNotPortedYet)
|
||||
|
||||
// NOTE(asaf): Any-project routes:
|
||||
anyProject.GET(hmnurl.RegexForumNewThread, authMiddleware(ForumNewThread))
|
||||
anyProject.POST(hmnurl.RegexForumNewThreadSubmit, authMiddleware(csrfMiddleware(ForumNewThreadSubmit)))
|
||||
anyProject.GET(hmnurl.RegexForumThread, ForumThread)
|
||||
anyProject.GET(hmnurl.RegexForum, Forum)
|
||||
anyProject.POST(hmnurl.RegexForumMarkRead, authMiddleware(csrfMiddleware(ForumMarkRead)))
|
||||
anyProject.GET(hmnurl.RegexForumPost, ForumPostRedirect)
|
||||
anyProject.GET(hmnurl.RegexForumPostReply, authMiddleware(ForumPostReply))
|
||||
anyProject.POST(hmnurl.RegexForumPostReply, authMiddleware(csrfMiddleware(ForumPostReplySubmit)))
|
||||
anyProject.GET(hmnurl.RegexForumPostEdit, authMiddleware(ForumPostEdit))
|
||||
anyProject.POST(hmnurl.RegexForumPostEdit, authMiddleware(csrfMiddleware(ForumPostEditSubmit)))
|
||||
anyProject.GET(hmnurl.RegexForumPostDelete, authMiddleware(ForumPostDelete))
|
||||
anyProject.POST(hmnurl.RegexForumPostDelete, authMiddleware(csrfMiddleware(ForumPostDeleteSubmit)))
|
||||
anyProject.GET(hmnurl.RegexWikiArticle, WikiArticleRedirect)
|
||||
attachProjectRoutes := func(rb *RouteBuilder) {
|
||||
rb.GET(hmnurl.RegexHomepage, func(c *RequestContext) ResponseData {
|
||||
if c.CurrentProject.IsHMN() {
|
||||
return Index(c)
|
||||
} else {
|
||||
return ProjectHomepage(c)
|
||||
}
|
||||
})
|
||||
|
||||
anyProject.GET(hmnurl.RegexBlog, BlogIndex)
|
||||
anyProject.GET(hmnurl.RegexBlogNewThread, authMiddleware(BlogNewThread))
|
||||
anyProject.POST(hmnurl.RegexBlogNewThread, authMiddleware(csrfMiddleware(BlogNewThreadSubmit)))
|
||||
anyProject.GET(hmnurl.RegexBlogThread, BlogThread)
|
||||
anyProject.GET(hmnurl.RegexBlogPost, BlogPostRedirectToThread)
|
||||
anyProject.GET(hmnurl.RegexBlogPostReply, authMiddleware(BlogPostReply))
|
||||
anyProject.POST(hmnurl.RegexBlogPostReply, authMiddleware(csrfMiddleware(BlogPostReplySubmit)))
|
||||
anyProject.GET(hmnurl.RegexBlogPostEdit, authMiddleware(BlogPostEdit))
|
||||
anyProject.POST(hmnurl.RegexBlogPostEdit, authMiddleware(csrfMiddleware(BlogPostEditSubmit)))
|
||||
anyProject.GET(hmnurl.RegexBlogPostDelete, authMiddleware(BlogPostDelete))
|
||||
anyProject.POST(hmnurl.RegexBlogPostDelete, authMiddleware(csrfMiddleware(BlogPostDeleteSubmit)))
|
||||
anyProject.GET(hmnurl.RegexBlogsRedirect, func(c *RequestContext) ResponseData {
|
||||
rb.POST(hmnurl.RegexForumNewThreadSubmit, authMiddleware(csrfMiddleware(ForumNewThreadSubmit)))
|
||||
rb.GET(hmnurl.RegexForumNewThread, authMiddleware(ForumNewThread))
|
||||
rb.GET(hmnurl.RegexForumThread, ForumThread)
|
||||
rb.GET(hmnurl.RegexForum, Forum)
|
||||
rb.POST(hmnurl.RegexForumMarkRead, authMiddleware(csrfMiddleware(ForumMarkRead)))
|
||||
rb.GET(hmnurl.RegexForumPost, ForumPostRedirect)
|
||||
rb.GET(hmnurl.RegexForumPostReply, authMiddleware(ForumPostReply))
|
||||
rb.POST(hmnurl.RegexForumPostReply, authMiddleware(csrfMiddleware(ForumPostReplySubmit)))
|
||||
rb.GET(hmnurl.RegexForumPostEdit, authMiddleware(ForumPostEdit))
|
||||
rb.POST(hmnurl.RegexForumPostEdit, authMiddleware(csrfMiddleware(ForumPostEditSubmit)))
|
||||
rb.GET(hmnurl.RegexForumPostDelete, authMiddleware(ForumPostDelete))
|
||||
rb.POST(hmnurl.RegexForumPostDelete, authMiddleware(csrfMiddleware(ForumPostDeleteSubmit)))
|
||||
rb.GET(hmnurl.RegexWikiArticle, WikiArticleRedirect)
|
||||
|
||||
rb.GET(hmnurl.RegexBlog, BlogIndex)
|
||||
rb.GET(hmnurl.RegexBlogNewThread, authMiddleware(BlogNewThread))
|
||||
rb.POST(hmnurl.RegexBlogNewThread, authMiddleware(csrfMiddleware(BlogNewThreadSubmit)))
|
||||
rb.GET(hmnurl.RegexBlogThread, BlogThread)
|
||||
rb.GET(hmnurl.RegexBlogPost, BlogPostRedirectToThread)
|
||||
rb.GET(hmnurl.RegexBlogPostReply, authMiddleware(BlogPostReply))
|
||||
rb.POST(hmnurl.RegexBlogPostReply, authMiddleware(csrfMiddleware(BlogPostReplySubmit)))
|
||||
rb.GET(hmnurl.RegexBlogPostEdit, authMiddleware(BlogPostEdit))
|
||||
rb.POST(hmnurl.RegexBlogPostEdit, authMiddleware(csrfMiddleware(BlogPostEditSubmit)))
|
||||
rb.GET(hmnurl.RegexBlogPostDelete, authMiddleware(BlogPostDelete))
|
||||
rb.POST(hmnurl.RegexBlogPostDelete, authMiddleware(csrfMiddleware(BlogPostDeleteSubmit)))
|
||||
rb.GET(hmnurl.RegexBlogsRedirect, func(c *RequestContext) ResponseData {
|
||||
return c.Redirect(hmnurl.ProjectUrl(
|
||||
fmt.Sprintf("blog%s", c.PathParams["remainder"]), nil,
|
||||
c.CurrentProject.Slug,
|
||||
), http.StatusMovedPermanently)
|
||||
})
|
||||
}
|
||||
hmnOnly.Group(hmnurl.RegexPersonalProject, func(rb *RouteBuilder) {
|
||||
// TODO(ben): Perhaps someday we can make this middleware modification feel better? It seems
|
||||
// pretty common to run the outermost middleware first before doing other stuff, but having
|
||||
// to nest functions this way feels real bad.
|
||||
rb.Middleware = func(h Handler) Handler {
|
||||
return hmnOnly.Middleware(func(c *RequestContext) ResponseData {
|
||||
// At this point we are definitely on the plain old HMN subdomain.
|
||||
|
||||
// Fetch personal project and do whatever
|
||||
id, err := strconv.Atoi(c.PathParams["projectid"])
|
||||
if err != nil {
|
||||
panic(oops.New(err, "project id was not numeric (bad regex in routing)"))
|
||||
}
|
||||
p, err := FetchProject(c.Context(), c.Conn, c.CurrentUser, id, ProjectsQuery{})
|
||||
if err != nil {
|
||||
if errors.Is(err, db.NotFound) {
|
||||
return FourOhFour(c)
|
||||
} else {
|
||||
return c.ErrorResponse(http.StatusInternalServerError, oops.New(err, "failed to fetch personal project"))
|
||||
}
|
||||
}
|
||||
|
||||
if !p.Project.Personal {
|
||||
// TODO: Redirect to the same page on the other prefix
|
||||
return c.Redirect(hmnurl.BuildOfficialProjectHomepage(p.Project.Slug), http.StatusSeeOther)
|
||||
}
|
||||
|
||||
c.CurrentProject = &p.Project
|
||||
|
||||
return h(c)
|
||||
})
|
||||
}
|
||||
attachProjectRoutes(rb)
|
||||
})
|
||||
anyProject.Group(hmnurl.RegexHomepage, func(rb *RouteBuilder) {
|
||||
rb.Middleware = func(h Handler) Handler {
|
||||
return anyProject.Middleware(func(c *RequestContext) ResponseData {
|
||||
// We could be on any project's subdomain.
|
||||
|
||||
// Check if the current project (matched by subdomain) is actually no longer official
|
||||
// and therefore needs to be redirected to the personal project version of the route.
|
||||
if c.CurrentProject.Personal {
|
||||
// TODO: Redirect to the same page on the other prefix
|
||||
return c.Redirect(hmnurl.BuildPersonalProject(c.CurrentProject.ID, c.CurrentProject.Slug), http.StatusSeeOther)
|
||||
}
|
||||
|
||||
return h(c)
|
||||
})
|
||||
}
|
||||
attachProjectRoutes(rb)
|
||||
})
|
||||
|
||||
anyProject.POST(hmnurl.RegexAssetUpload, AssetUpload)
|
||||
|
||||
|
@ -378,17 +430,31 @@ func LoadCommonWebsiteData(c *RequestContext) (bool, ResponseData) {
|
|||
slug := strings.TrimRight(hostPrefix, ".")
|
||||
|
||||
dbProject, err := FetchProjectBySlug(c.Context(), c.Conn, c.CurrentUser, slug, ProjectsQuery{})
|
||||
if err != nil {
|
||||
if err == nil {
|
||||
c.CurrentProject = &dbProject.Project
|
||||
} else {
|
||||
if errors.Is(err, db.NotFound) {
|
||||
return false, c.Redirect(hmnurl.BuildHomepage(), http.StatusSeeOther)
|
||||
// do nothing, this is fine
|
||||
} else {
|
||||
return false, c.ErrorResponse(http.StatusInternalServerError, oops.New(err, "failed to fetch current project"))
|
||||
}
|
||||
}
|
||||
|
||||
if c.CurrentProject == nil {
|
||||
dbProject, err := FetchProject(c.Context(), c.Conn, c.CurrentUser, models.HMNProjectID, ProjectsQuery{
|
||||
IncludeHidden: true,
|
||||
})
|
||||
if err != nil {
|
||||
panic(oops.New(err, "failed to fetch HMN project"))
|
||||
}
|
||||
c.CurrentProject = &dbProject.Project
|
||||
}
|
||||
|
||||
if c.CurrentProject == nil {
|
||||
panic("failed to load project data")
|
||||
}
|
||||
}
|
||||
|
||||
theme := "light"
|
||||
if c.CurrentUser != nil && c.CurrentUser.DarkTheme {
|
||||
theme = "dark"
|
||||
|
|
Loading…
Reference in New Issue