mirror of https://github.com/jlelse/GoBlog
Improve http router setup (use middlewares instead of custom functions)
This commit is contained in:
parent
04f5cdc122
commit
bbbabdb335
81
blogstats.go
81
blogstats.go
|
@ -4,46 +4,45 @@ import (
|
||||||
"net/http"
|
"net/http"
|
||||||
)
|
)
|
||||||
|
|
||||||
func serveBlogStats(blog, statsPath string) func(http.ResponseWriter, *http.Request) {
|
func serveBlogStats(w http.ResponseWriter, r *http.Request) {
|
||||||
return func(w http.ResponseWriter, r *http.Request) {
|
blog := r.Context().Value(blogContextKey).(string)
|
||||||
// Build query
|
// Build query
|
||||||
query, params := buildPostsQuery(&postsRequestConfig{
|
query, params := buildPostsQuery(&postsRequestConfig{
|
||||||
blog: blog,
|
blog: blog,
|
||||||
status: statusPublished,
|
status: statusPublished,
|
||||||
})
|
})
|
||||||
// Count total posts
|
// Count total posts
|
||||||
row, err := appDbQueryRow("select count(distinct path) from ("+query+")", params...)
|
row, err := appDbQueryRow("select count(distinct path) from ("+query+")", params...)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
serveError(w, r, err.Error(), http.StatusInternalServerError)
|
serveError(w, r, err.Error(), http.StatusInternalServerError)
|
||||||
return
|
return
|
||||||
}
|
|
||||||
var totalCount int
|
|
||||||
if err = row.Scan(&totalCount); err != nil {
|
|
||||||
serveError(w, r, err.Error(), http.StatusInternalServerError)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
// Count posts per year
|
|
||||||
rows, err := appDbQuery("select substr(published, 1, 4) as year, count(distinct path) as count from ("+query+") where published != '' group by year order by year desc", params...)
|
|
||||||
if err != nil {
|
|
||||||
serveError(w, r, err.Error(), http.StatusInternalServerError)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
var years, counts []int
|
|
||||||
for rows.Next() {
|
|
||||||
var year, count int
|
|
||||||
if err = rows.Scan(&year, &count); err == nil {
|
|
||||||
years = append(years, year)
|
|
||||||
counts = append(counts, count)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
render(w, r, templateBlogStats, &renderData{
|
|
||||||
BlogString: blog,
|
|
||||||
Canonical: statsPath,
|
|
||||||
Data: map[string]interface{}{
|
|
||||||
"total": totalCount,
|
|
||||||
"years": years,
|
|
||||||
"counts": counts,
|
|
||||||
},
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
var totalCount int
|
||||||
|
if err = row.Scan(&totalCount); err != nil {
|
||||||
|
serveError(w, r, err.Error(), http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// Count posts per year
|
||||||
|
rows, err := appDbQuery("select substr(published, 1, 4) as year, count(distinct path) as count from ("+query+") where published != '' group by year order by year desc", params...)
|
||||||
|
if err != nil {
|
||||||
|
serveError(w, r, err.Error(), http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
var years, counts []int
|
||||||
|
for rows.Next() {
|
||||||
|
var year, count int
|
||||||
|
if err = rows.Scan(&year, &count); err == nil {
|
||||||
|
years = append(years, year)
|
||||||
|
counts = append(counts, count)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
render(w, r, templateBlogStats, &renderData{
|
||||||
|
BlogString: blog,
|
||||||
|
Canonical: blogPath(blog) + appConfig.Blogs[blog].BlogStats.Path,
|
||||||
|
Data: map[string]interface{}{
|
||||||
|
"total": totalCount,
|
||||||
|
"years": years,
|
||||||
|
"counts": counts,
|
||||||
|
},
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
119
comments.go
119
comments.go
|
@ -20,70 +20,67 @@ type comment struct {
|
||||||
Comment string
|
Comment string
|
||||||
}
|
}
|
||||||
|
|
||||||
func serveComment(blog string) func(http.ResponseWriter, *http.Request) {
|
func serveComment(w http.ResponseWriter, r *http.Request) {
|
||||||
return func(w http.ResponseWriter, r *http.Request) {
|
id, err := strconv.Atoi(chi.URLParam(r, "id"))
|
||||||
id, err := strconv.Atoi(chi.URLParam(r, "id"))
|
if err != nil {
|
||||||
if err != nil {
|
serveError(w, r, err.Error(), http.StatusBadRequest)
|
||||||
serveError(w, r, err.Error(), http.StatusBadRequest)
|
return
|
||||||
return
|
|
||||||
}
|
|
||||||
row, err := appDbQueryRow("select id, target, name, website, comment from comments where id = @id", sql.Named("id", id))
|
|
||||||
if err != nil {
|
|
||||||
serveError(w, r, err.Error(), http.StatusInternalServerError)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
comment := &comment{}
|
|
||||||
if err = row.Scan(&comment.ID, &comment.Target, &comment.Name, &comment.Website, &comment.Comment); err == sql.ErrNoRows {
|
|
||||||
serve404(w, r)
|
|
||||||
return
|
|
||||||
} else if err != nil {
|
|
||||||
serveError(w, r, err.Error(), http.StatusInternalServerError)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
w.Header().Set("X-Robots-Tag", "noindex")
|
|
||||||
render(w, r, templateComment, &renderData{
|
|
||||||
BlogString: blog,
|
|
||||||
Canonical: appConfig.Server.PublicAddress + appConfig.Blogs[blog].getRelativePath(fmt.Sprintf("/comment/%d", id)),
|
|
||||||
Data: comment,
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
row, err := appDbQueryRow("select id, target, name, website, comment from comments where id = @id", sql.Named("id", id))
|
||||||
|
if err != nil {
|
||||||
|
serveError(w, r, err.Error(), http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
comment := &comment{}
|
||||||
|
if err = row.Scan(&comment.ID, &comment.Target, &comment.Name, &comment.Website, &comment.Comment); err == sql.ErrNoRows {
|
||||||
|
serve404(w, r)
|
||||||
|
return
|
||||||
|
} else if err != nil {
|
||||||
|
serveError(w, r, err.Error(), http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
w.Header().Set("X-Robots-Tag", "noindex")
|
||||||
|
blog := r.Context().Value(blogContextKey).(string)
|
||||||
|
render(w, r, templateComment, &renderData{
|
||||||
|
BlogString: blog,
|
||||||
|
Canonical: appConfig.Server.PublicAddress + appConfig.Blogs[blog].getRelativePath(fmt.Sprintf("/comment/%d", id)),
|
||||||
|
Data: comment,
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func createComment(blog, commentsPath string) func(http.ResponseWriter, *http.Request) {
|
func createComment(w http.ResponseWriter, r *http.Request) {
|
||||||
return func(w http.ResponseWriter, r *http.Request) {
|
// Check target
|
||||||
// Check target
|
target := checkCommentTarget(w, r)
|
||||||
target := checkCommentTarget(w, r)
|
if target == "" {
|
||||||
if target == "" {
|
return
|
||||||
return
|
}
|
||||||
}
|
// Check and clean comment
|
||||||
// Check and clean comment
|
strict := bluemonday.StrictPolicy()
|
||||||
strict := bluemonday.StrictPolicy()
|
comment := strings.TrimSpace(strict.Sanitize(r.FormValue("comment")))
|
||||||
comment := strings.TrimSpace(strict.Sanitize(r.FormValue("comment")))
|
if comment == "" {
|
||||||
if comment == "" {
|
serveError(w, r, "Comment is empty", http.StatusBadRequest)
|
||||||
serveError(w, r, "Comment is empty", http.StatusBadRequest)
|
return
|
||||||
return
|
}
|
||||||
}
|
name := strings.TrimSpace(strict.Sanitize(r.FormValue("name")))
|
||||||
name := strings.TrimSpace(strict.Sanitize(r.FormValue("name")))
|
if name == "" {
|
||||||
if name == "" {
|
name = "Anonymous"
|
||||||
name = "Anonymous"
|
}
|
||||||
}
|
website := strings.TrimSpace(strict.Sanitize(r.FormValue("website")))
|
||||||
website := strings.TrimSpace(strict.Sanitize(r.FormValue("website")))
|
// Insert
|
||||||
// Insert
|
result, err := appDbExec("insert into comments (target, comment, name, website) values (@target, @comment, @name, @website)", sql.Named("target", target), sql.Named("comment", comment), sql.Named("name", name), sql.Named("website", website))
|
||||||
result, err := appDbExec("insert into comments (target, comment, name, website) values (@target, @comment, @name, @website)", sql.Named("target", target), sql.Named("comment", comment), sql.Named("name", name), sql.Named("website", website))
|
if err != nil {
|
||||||
if err != nil {
|
serveError(w, r, err.Error(), http.StatusInternalServerError)
|
||||||
serveError(w, r, err.Error(), http.StatusInternalServerError)
|
return
|
||||||
return
|
}
|
||||||
}
|
if commentID, err := result.LastInsertId(); err != nil {
|
||||||
if commentID, err := result.LastInsertId(); err != nil {
|
// Serve error
|
||||||
// Serve error
|
serveError(w, r, err.Error(), http.StatusInternalServerError)
|
||||||
serveError(w, r, err.Error(), http.StatusInternalServerError)
|
} else {
|
||||||
} else {
|
commentAddress := fmt.Sprintf("%s/%d", blogPath(r.Context().Value(blogContextKey).(string))+"/comment", commentID)
|
||||||
commentAddress := fmt.Sprintf("%s/%d", commentsPath, commentID)
|
// Send webmention
|
||||||
// Send webmention
|
_ = createWebmention(appConfig.Server.PublicAddress+commentAddress, appConfig.Server.PublicAddress+target)
|
||||||
_ = createWebmention(appConfig.Server.PublicAddress+commentAddress, appConfig.Server.PublicAddress+target)
|
// Redirect to comment
|
||||||
// Redirect to comment
|
http.Redirect(w, r, commentAddress, http.StatusFound)
|
||||||
http.Redirect(w, r, commentAddress, http.StatusFound)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -33,53 +33,53 @@ func (p *commentsPaginationAdapter) Slice(offset, length int, data interface{})
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
func commentsAdmin(blog, commentPath string) func(http.ResponseWriter, *http.Request) {
|
func commentsAdmin(w http.ResponseWriter, r *http.Request) {
|
||||||
return func(w http.ResponseWriter, r *http.Request) {
|
blog := r.Context().Value(blogContextKey).(string)
|
||||||
// Adapter
|
commentsPath := r.Context().Value(pathContextKey).(string)
|
||||||
pageNoString := chi.URLParam(r, "page")
|
// Adapter
|
||||||
pageNo, _ := strconv.Atoi(pageNoString)
|
pageNoString := chi.URLParam(r, "page")
|
||||||
p := paginator.New(&commentsPaginationAdapter{config: &commentsRequestConfig{}}, 5)
|
pageNo, _ := strconv.Atoi(pageNoString)
|
||||||
p.SetPage(pageNo)
|
p := paginator.New(&commentsPaginationAdapter{config: &commentsRequestConfig{}}, 5)
|
||||||
var comments []*comment
|
p.SetPage(pageNo)
|
||||||
err := p.Results(&comments)
|
var comments []*comment
|
||||||
if err != nil {
|
err := p.Results(&comments)
|
||||||
serveError(w, r, err.Error(), http.StatusInternalServerError)
|
if err != nil {
|
||||||
return
|
serveError(w, r, err.Error(), http.StatusInternalServerError)
|
||||||
}
|
return
|
||||||
// Navigation
|
|
||||||
var hasPrev, hasNext bool
|
|
||||||
var prevPage, nextPage int
|
|
||||||
var prevPath, nextPath string
|
|
||||||
hasPrev, _ = p.HasPrev()
|
|
||||||
if hasPrev {
|
|
||||||
prevPage, _ = p.PrevPage()
|
|
||||||
} else {
|
|
||||||
prevPage, _ = p.Page()
|
|
||||||
}
|
|
||||||
if prevPage < 2 {
|
|
||||||
prevPath = commentPath
|
|
||||||
} else {
|
|
||||||
prevPath = fmt.Sprintf("%s/page/%d", commentPath, prevPage)
|
|
||||||
}
|
|
||||||
hasNext, _ = p.HasNext()
|
|
||||||
if hasNext {
|
|
||||||
nextPage, _ = p.NextPage()
|
|
||||||
} else {
|
|
||||||
nextPage, _ = p.Page()
|
|
||||||
}
|
|
||||||
nextPath = fmt.Sprintf("%s/page/%d", commentPath, nextPage)
|
|
||||||
// Render
|
|
||||||
render(w, r, templateCommentsAdmin, &renderData{
|
|
||||||
BlogString: blog,
|
|
||||||
Data: map[string]interface{}{
|
|
||||||
"Comments": comments,
|
|
||||||
"HasPrev": hasPrev,
|
|
||||||
"HasNext": hasNext,
|
|
||||||
"Prev": slashIfEmpty(prevPath),
|
|
||||||
"Next": slashIfEmpty(nextPath),
|
|
||||||
},
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
// Navigation
|
||||||
|
var hasPrev, hasNext bool
|
||||||
|
var prevPage, nextPage int
|
||||||
|
var prevPath, nextPath string
|
||||||
|
hasPrev, _ = p.HasPrev()
|
||||||
|
if hasPrev {
|
||||||
|
prevPage, _ = p.PrevPage()
|
||||||
|
} else {
|
||||||
|
prevPage, _ = p.Page()
|
||||||
|
}
|
||||||
|
if prevPage < 2 {
|
||||||
|
prevPath = commentsPath
|
||||||
|
} else {
|
||||||
|
prevPath = fmt.Sprintf("%s/page/%d", commentsPath, prevPage)
|
||||||
|
}
|
||||||
|
hasNext, _ = p.HasNext()
|
||||||
|
if hasNext {
|
||||||
|
nextPage, _ = p.NextPage()
|
||||||
|
} else {
|
||||||
|
nextPage, _ = p.Page()
|
||||||
|
}
|
||||||
|
nextPath = fmt.Sprintf("%s/page/%d", commentsPath, nextPage)
|
||||||
|
// Render
|
||||||
|
render(w, r, templateCommentsAdmin, &renderData{
|
||||||
|
BlogString: blog,
|
||||||
|
Data: map[string]interface{}{
|
||||||
|
"Comments": comments,
|
||||||
|
"HasPrev": hasPrev,
|
||||||
|
"HasNext": hasNext,
|
||||||
|
"Prev": slashIfEmpty(prevPath),
|
||||||
|
"Next": slashIfEmpty(nextPath),
|
||||||
|
},
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func commentsAdminDelete(w http.ResponseWriter, r *http.Request) {
|
func commentsAdminDelete(w http.ResponseWriter, r *http.Request) {
|
||||||
|
|
|
@ -2,19 +2,20 @@ package main
|
||||||
|
|
||||||
import "net/http"
|
import "net/http"
|
||||||
|
|
||||||
func serveCustomPage(blog *configBlog, page *customPage) func(http.ResponseWriter, *http.Request) {
|
const customPageContextKey = "custompage"
|
||||||
return func(w http.ResponseWriter, r *http.Request) {
|
|
||||||
if appConfig.Cache != nil && appConfig.Cache.Enable && page.Cache {
|
func serveCustomPage(w http.ResponseWriter, r *http.Request) {
|
||||||
if page.CacheExpiration != 0 {
|
page := r.Context().Value(customPageContextKey).(*customPage)
|
||||||
setInternalCacheExpirationHeader(w, page.CacheExpiration)
|
if appConfig.Cache != nil && appConfig.Cache.Enable && page.Cache {
|
||||||
} else {
|
if page.CacheExpiration != 0 {
|
||||||
setInternalCacheExpirationHeader(w, int(appConfig.Cache.Expiration))
|
setInternalCacheExpirationHeader(w, page.CacheExpiration)
|
||||||
}
|
} else {
|
||||||
|
setInternalCacheExpirationHeader(w, int(appConfig.Cache.Expiration))
|
||||||
}
|
}
|
||||||
render(w, r, page.Template, &renderData{
|
|
||||||
Blog: blog,
|
|
||||||
Canonical: appConfig.Server.PublicAddress + page.Path,
|
|
||||||
Data: page.Data,
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
render(w, r, page.Template, &renderData{
|
||||||
|
BlogString: r.Context().Value(blogContextKey).(string),
|
||||||
|
Canonical: appConfig.Server.PublicAddress + page.Path,
|
||||||
|
Data: page.Data,
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
124
editor.go
124
editor.go
|
@ -11,74 +11,72 @@ import (
|
||||||
|
|
||||||
const editorPath = "/editor"
|
const editorPath = "/editor"
|
||||||
|
|
||||||
func serveEditor(blog string) func(http.ResponseWriter, *http.Request) {
|
func serveEditor(w http.ResponseWriter, r *http.Request) {
|
||||||
return func(w http.ResponseWriter, r *http.Request) {
|
blog := r.Context().Value(blogContextKey).(string)
|
||||||
render(w, r, templateEditor, &renderData{
|
render(w, r, templateEditor, &renderData{
|
||||||
BlogString: blog,
|
BlogString: blog,
|
||||||
Data: map[string]interface{}{
|
Data: map[string]interface{}{
|
||||||
"Drafts": loadDrafts(blog),
|
"Drafts": loadDrafts(blog),
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func serveEditorPost(blog string) func(w http.ResponseWriter, r *http.Request) {
|
func serveEditorPost(w http.ResponseWriter, r *http.Request) {
|
||||||
return func(w http.ResponseWriter, r *http.Request) {
|
blog := r.Context().Value(blogContextKey).(string)
|
||||||
if action := r.FormValue("editoraction"); action != "" {
|
if action := r.FormValue("editoraction"); action != "" {
|
||||||
switch action {
|
switch action {
|
||||||
case "loadupdate":
|
case "loadupdate":
|
||||||
parsedURL, err := url.Parse(r.FormValue("url"))
|
parsedURL, err := url.Parse(r.FormValue("url"))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
serveError(w, r, err.Error(), http.StatusBadRequest)
|
serveError(w, r, err.Error(), http.StatusBadRequest)
|
||||||
return
|
return
|
||||||
}
|
|
||||||
post, err := getPost(parsedURL.Path)
|
|
||||||
if err != nil {
|
|
||||||
serveError(w, r, err.Error(), http.StatusBadRequest)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
mf := post.toMfItem()
|
|
||||||
render(w, r, templateEditor, &renderData{
|
|
||||||
BlogString: blog,
|
|
||||||
Data: map[string]interface{}{
|
|
||||||
"UpdatePostURL": parsedURL.String(),
|
|
||||||
"UpdatePostContent": mf.Properties.Content[0],
|
|
||||||
"Drafts": loadDrafts(blog),
|
|
||||||
},
|
|
||||||
})
|
|
||||||
case "updatepost":
|
|
||||||
urlValue := r.FormValue("url")
|
|
||||||
content := r.FormValue("content")
|
|
||||||
mf := map[string]interface{}{
|
|
||||||
"action": actionUpdate,
|
|
||||||
"url": urlValue,
|
|
||||||
"replace": map[string][]string{
|
|
||||||
"content": {
|
|
||||||
content,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
jsonBytes, err := json.Marshal(mf)
|
|
||||||
if err != nil {
|
|
||||||
serveError(w, r, err.Error(), http.StatusInternalServerError)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
req, err := http.NewRequest(http.MethodPost, "", bytes.NewReader(jsonBytes))
|
|
||||||
if err != nil {
|
|
||||||
serveError(w, r, err.Error(), http.StatusInternalServerError)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
req.Header.Set(contentType, contentTypeJSON)
|
|
||||||
editorMicropubPost(w, req, false)
|
|
||||||
case "upload":
|
|
||||||
editorMicropubPost(w, r, true)
|
|
||||||
default:
|
|
||||||
serveError(w, r, "Unknown editoraction", http.StatusBadRequest)
|
|
||||||
}
|
}
|
||||||
return
|
post, err := getPost(parsedURL.Path)
|
||||||
|
if err != nil {
|
||||||
|
serveError(w, r, err.Error(), http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
mf := post.toMfItem()
|
||||||
|
render(w, r, templateEditor, &renderData{
|
||||||
|
BlogString: blog,
|
||||||
|
Data: map[string]interface{}{
|
||||||
|
"UpdatePostURL": parsedURL.String(),
|
||||||
|
"UpdatePostContent": mf.Properties.Content[0],
|
||||||
|
"Drafts": loadDrafts(blog),
|
||||||
|
},
|
||||||
|
})
|
||||||
|
case "updatepost":
|
||||||
|
urlValue := r.FormValue("url")
|
||||||
|
content := r.FormValue("content")
|
||||||
|
mf := map[string]interface{}{
|
||||||
|
"action": actionUpdate,
|
||||||
|
"url": urlValue,
|
||||||
|
"replace": map[string][]string{
|
||||||
|
"content": {
|
||||||
|
content,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
jsonBytes, err := json.Marshal(mf)
|
||||||
|
if err != nil {
|
||||||
|
serveError(w, r, err.Error(), http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
req, err := http.NewRequest(http.MethodPost, "", bytes.NewReader(jsonBytes))
|
||||||
|
if err != nil {
|
||||||
|
serveError(w, r, err.Error(), http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
req.Header.Set(contentType, contentTypeJSON)
|
||||||
|
editorMicropubPost(w, req, false)
|
||||||
|
case "upload":
|
||||||
|
editorMicropubPost(w, r, true)
|
||||||
|
default:
|
||||||
|
serveError(w, r, "Unknown editoraction", http.StatusBadRequest)
|
||||||
}
|
}
|
||||||
editorMicropubPost(w, r, false)
|
return
|
||||||
}
|
}
|
||||||
|
editorMicropubPost(w, r, false)
|
||||||
}
|
}
|
||||||
|
|
||||||
func loadDrafts(blog string) []*post {
|
func loadDrafts(blog string) []*post {
|
||||||
|
|
6
go.mod
6
go.mod
|
@ -44,7 +44,7 @@ require (
|
||||||
github.com/pquerna/otp v1.3.0
|
github.com/pquerna/otp v1.3.0
|
||||||
github.com/smartystreets/assertions v1.2.0 // indirect
|
github.com/smartystreets/assertions v1.2.0 // indirect
|
||||||
github.com/snabb/sitemap v1.0.0
|
github.com/snabb/sitemap v1.0.0
|
||||||
github.com/spf13/afero v1.5.1 // indirect
|
github.com/spf13/afero v1.6.0 // indirect
|
||||||
github.com/spf13/cast v1.3.1
|
github.com/spf13/cast v1.3.1
|
||||||
github.com/spf13/jwalterweatherman v1.1.0 // indirect
|
github.com/spf13/jwalterweatherman v1.1.0 // indirect
|
||||||
github.com/spf13/viper v1.7.1
|
github.com/spf13/viper v1.7.1
|
||||||
|
@ -52,7 +52,7 @@ require (
|
||||||
github.com/thoas/go-funk v0.8.0
|
github.com/thoas/go-funk v0.8.0
|
||||||
github.com/tomnomnom/linkheader v0.0.0-20180905144013-02ca5825eb80
|
github.com/tomnomnom/linkheader v0.0.0-20180905144013-02ca5825eb80
|
||||||
github.com/vcraescu/go-paginator v1.0.1-0.20201114172518-2cfc59fe05c2
|
github.com/vcraescu/go-paginator v1.0.1-0.20201114172518-2cfc59fe05c2
|
||||||
github.com/yuin/goldmark v1.3.2
|
github.com/yuin/goldmark v1.3.3
|
||||||
github.com/yuin/goldmark-emoji v1.0.1
|
github.com/yuin/goldmark-emoji v1.0.1
|
||||||
go.uber.org/multierr v1.6.0 // indirect
|
go.uber.org/multierr v1.6.0 // indirect
|
||||||
go.uber.org/zap v1.16.0 // indirect
|
go.uber.org/zap v1.16.0 // indirect
|
||||||
|
@ -61,7 +61,7 @@ require (
|
||||||
golang.org/x/mod v0.4.1 // indirect
|
golang.org/x/mod v0.4.1 // indirect
|
||||||
golang.org/x/net v0.0.0-20210316092652-d523dce5a7f4 // indirect
|
golang.org/x/net v0.0.0-20210316092652-d523dce5a7f4 // indirect
|
||||||
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c
|
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c
|
||||||
golang.org/x/sys v0.0.0-20210319071255-635bc2c9138d // indirect
|
golang.org/x/sys v0.0.0-20210320140829-1e4c9ba3b0c4 // indirect
|
||||||
golang.org/x/term v0.0.0-20201210144234-2321bbc49cbf // indirect
|
golang.org/x/term v0.0.0-20201210144234-2321bbc49cbf // indirect
|
||||||
golang.org/x/text v0.3.5 // indirect
|
golang.org/x/text v0.3.5 // indirect
|
||||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect
|
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect
|
||||||
|
|
12
go.sum
12
go.sum
|
@ -299,8 +299,8 @@ github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72 h1:qLC7fQah7D6K1
|
||||||
github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
|
github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
|
||||||
github.com/spf13/afero v0.0.0-20170901052352-ee1bd8ee15a1/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ=
|
github.com/spf13/afero v0.0.0-20170901052352-ee1bd8ee15a1/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ=
|
||||||
github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ=
|
github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ=
|
||||||
github.com/spf13/afero v1.5.1 h1:VHu76Lk0LSP1x254maIu2bplkWpfBWI+B+6fdoZprcg=
|
github.com/spf13/afero v1.6.0 h1:xoax2sJ2DT8S8xA2paPFjDCScCNeWsg75VG0DLRreiY=
|
||||||
github.com/spf13/afero v1.5.1/go.mod h1:Ai8FlHk4v/PARR026UzYexafAt9roJ7LcLMAmO6Z93I=
|
github.com/spf13/afero v1.6.0/go.mod h1:Ai8FlHk4v/PARR026UzYexafAt9roJ7LcLMAmO6Z93I=
|
||||||
github.com/spf13/cast v1.1.0/go.mod h1:r2rcYCSwa1IExKTDiTfzaxqT2FNHs8hODu4LnUfgKEg=
|
github.com/spf13/cast v1.1.0/go.mod h1:r2rcYCSwa1IExKTDiTfzaxqT2FNHs8hODu4LnUfgKEg=
|
||||||
github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
|
github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
|
||||||
github.com/spf13/cast v1.3.1 h1:nFm6S0SMdyzrzcmThSipiEubIDy8WEXKNZ0UOgiRpng=
|
github.com/spf13/cast v1.3.1 h1:nFm6S0SMdyzrzcmThSipiEubIDy8WEXKNZ0UOgiRpng=
|
||||||
|
@ -340,8 +340,8 @@ github.com/vcraescu/go-paginator v1.0.1-0.20201114172518-2cfc59fe05c2 h1:l5j4nE6
|
||||||
github.com/vcraescu/go-paginator v1.0.1-0.20201114172518-2cfc59fe05c2/go.mod h1:NEDNuq1asYbAeX+uy6w56MDQSFmBQz9k+N9Hy6m4r2U=
|
github.com/vcraescu/go-paginator v1.0.1-0.20201114172518-2cfc59fe05c2/go.mod h1:NEDNuq1asYbAeX+uy6w56MDQSFmBQz9k+N9Hy6m4r2U=
|
||||||
github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU=
|
github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU=
|
||||||
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||||
github.com/yuin/goldmark v1.3.2 h1:YjHC5TgyMmHpicTgEqDN0Q96Xo8K6tLXPnmNOHXCgs0=
|
github.com/yuin/goldmark v1.3.3 h1:37BdQwPx8VOSic8eDSWee6QL9mRpZRm9VJp/QugNrW0=
|
||||||
github.com/yuin/goldmark v1.3.2/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
|
github.com/yuin/goldmark v1.3.3/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
|
||||||
github.com/yuin/goldmark-emoji v1.0.1 h1:ctuWEyzGBwiucEqxzwe0SOYDXPAucOrE9NQC18Wa1os=
|
github.com/yuin/goldmark-emoji v1.0.1 h1:ctuWEyzGBwiucEqxzwe0SOYDXPAucOrE9NQC18Wa1os=
|
||||||
github.com/yuin/goldmark-emoji v1.0.1/go.mod h1:2w1E6FEWLcDQkoTE+7HU6QF1F6SLlNGjRIBbIZQFqkQ=
|
github.com/yuin/goldmark-emoji v1.0.1/go.mod h1:2w1E6FEWLcDQkoTE+7HU6QF1F6SLlNGjRIBbIZQFqkQ=
|
||||||
go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU=
|
go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU=
|
||||||
|
@ -453,8 +453,8 @@ golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7w
|
||||||
golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20210303074136-134d130e1a04/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20210303074136-134d130e1a04/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20210315160823-c6e025ad8005/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20210315160823-c6e025ad8005/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20210319071255-635bc2c9138d h1:jbzgAvDZn8aEnytae+4ou0J0GwFZoHR0hOrTg4qH8GA=
|
golang.org/x/sys v0.0.0-20210320140829-1e4c9ba3b0c4 h1:EZ2mChiOa8udjfp6rRmswTbtZN/QzUQp4ptM4rnjHvc=
|
||||||
golang.org/x/sys v0.0.0-20210319071255-635bc2c9138d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20210320140829-1e4c9ba3b0c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||||
golang.org/x/term v0.0.0-20201210144234-2321bbc49cbf h1:MZ2shdL+ZM/XzY3ZGOnh4Nlpnxz5GSOhOmtHo3iPU6M=
|
golang.org/x/term v0.0.0-20201210144234-2321bbc49cbf h1:MZ2shdL+ZM/XzY3ZGOnh4Nlpnxz5GSOhOmtHo3iPU6M=
|
||||||
golang.org/x/term v0.0.0-20201210144234-2321bbc49cbf/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
golang.org/x/term v0.0.0-20201210144234-2321bbc49cbf/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||||
|
|
240
http.go
240
http.go
|
@ -9,7 +9,6 @@ import (
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/caddyserver/certmagic"
|
"github.com/caddyserver/certmagic"
|
||||||
"github.com/dchest/captcha"
|
"github.com/dchest/captcha"
|
||||||
|
@ -110,6 +109,8 @@ var (
|
||||||
privateMode = false
|
privateMode = false
|
||||||
privateModeHandler = []func(http.Handler) http.Handler{}
|
privateModeHandler = []func(http.Handler) http.Handler{}
|
||||||
|
|
||||||
|
setBlogMiddlewares = map[string]func(http.Handler) http.Handler{}
|
||||||
|
|
||||||
captchaHandler http.Handler
|
captchaHandler http.Handler
|
||||||
|
|
||||||
micropubRouter *chi.Mux
|
micropubRouter *chi.Mux
|
||||||
|
@ -117,10 +118,9 @@ var (
|
||||||
webmentionsRouter *chi.Mux
|
webmentionsRouter *chi.Mux
|
||||||
notificationsRouter *chi.Mux
|
notificationsRouter *chi.Mux
|
||||||
activitypubRouter *chi.Mux
|
activitypubRouter *chi.Mux
|
||||||
|
editorRouter *chi.Mux
|
||||||
editorRouters = map[string]*chi.Mux{}
|
commentsRouter *chi.Mux
|
||||||
commentRouters = map[string]*chi.Mux{}
|
searchRouter *chi.Mux
|
||||||
searchRouters = map[string]*chi.Mux{}
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func buildStaticHandlersRouters() error {
|
func buildStaticHandlersRouters() error {
|
||||||
|
@ -157,9 +157,8 @@ func buildStaticHandlersRouters() error {
|
||||||
|
|
||||||
notificationsRouter = chi.NewRouter()
|
notificationsRouter = chi.NewRouter()
|
||||||
notificationsRouter.Use(authMiddleware)
|
notificationsRouter.Use(authMiddleware)
|
||||||
notificationsHandler := notificationsAdmin(notificationsPath)
|
notificationsRouter.Get("/", notificationsAdmin)
|
||||||
notificationsRouter.Get("/", notificationsHandler)
|
notificationsRouter.Get(paginationPath, notificationsAdmin)
|
||||||
notificationsRouter.Get(paginationPath, notificationsHandler)
|
|
||||||
|
|
||||||
if ap := appConfig.ActivityPub; ap != nil && ap.Enabled {
|
if ap := appConfig.ActivityPub; ap != nil && ap.Enabled {
|
||||||
activitypubRouter = chi.NewRouter()
|
activitypubRouter = chi.NewRouter()
|
||||||
|
@ -167,55 +166,42 @@ func buildStaticHandlersRouters() error {
|
||||||
activitypubRouter.Post("/{blog}/inbox", apHandleInbox)
|
activitypubRouter.Post("/{blog}/inbox", apHandleInbox)
|
||||||
}
|
}
|
||||||
|
|
||||||
for blog, blogConfig := range appConfig.Blogs {
|
editorRouter = chi.NewRouter()
|
||||||
blogPath := blogPath(blogConfig)
|
editorRouter.Use(authMiddleware)
|
||||||
|
editorRouter.Get("/", serveEditor)
|
||||||
|
editorRouter.Post("/", serveEditorPost)
|
||||||
|
|
||||||
editorRouter := chi.NewRouter()
|
commentsRouter = chi.NewRouter()
|
||||||
editorRouter.Use(authMiddleware)
|
commentsRouter.Use(privateModeHandler...)
|
||||||
editorRouter.Get("/", serveEditor(blog))
|
commentsRouter.With(cacheMiddleware).Get("/{id:[0-9]+}", serveComment)
|
||||||
editorRouter.Post("/", serveEditorPost(blog))
|
commentsRouter.With(captchaMiddleware).Post("/", createComment)
|
||||||
editorRouters[blog] = editorRouter
|
commentsRouter.Group(func(r chi.Router) {
|
||||||
|
// Admin
|
||||||
|
r.Use(authMiddleware)
|
||||||
|
r.Get("/", commentsAdmin)
|
||||||
|
r.Get(paginationPath, commentsAdmin)
|
||||||
|
r.Post("/delete", commentsAdminDelete)
|
||||||
|
})
|
||||||
|
|
||||||
if commentsConfig := blogConfig.Comments; commentsConfig != nil && commentsConfig.Enabled {
|
searchRouter = chi.NewRouter()
|
||||||
commentsPath := blogPath + "/comment"
|
searchRouter.Use(privateModeHandler...)
|
||||||
commentRouter := chi.NewRouter()
|
searchRouter.Use(cacheMiddleware)
|
||||||
commentRouter.Use(privateModeHandler...)
|
searchRouter.Get("/", serveSearch)
|
||||||
commentRouter.With(cacheMiddleware).Get("/{id:[0-9]+}", serveComment(blog))
|
searchRouter.Post("/", serveSearch)
|
||||||
commentRouter.With(captchaMiddleware).Post("/", createComment(blog, commentsPath))
|
searchResultPath := "/" + searchPlaceholder
|
||||||
// Admin
|
searchRouter.Get(searchResultPath, serveSearchResult)
|
||||||
commentRouter.Group(func(r chi.Router) {
|
searchRouter.Get(searchResultPath+feedPath, serveSearchResult)
|
||||||
r.Use(authMiddleware)
|
searchRouter.Get(searchResultPath+paginationPath, serveSearchResult)
|
||||||
handler := commentsAdmin(blog, commentsPath)
|
|
||||||
r.Get("/", handler)
|
|
||||||
r.Get(paginationPath, handler)
|
|
||||||
r.Post("/delete", commentsAdminDelete)
|
|
||||||
})
|
|
||||||
commentRouters[blog] = commentRouter
|
|
||||||
}
|
|
||||||
|
|
||||||
if blogConfig.Search != nil && blogConfig.Search.Enabled {
|
for blog := range appConfig.Blogs {
|
||||||
searchPath := blogPath + blogConfig.Search.Path
|
sbm := middleware.WithValue(blogContextKey, blog)
|
||||||
searchRouter := chi.NewRouter()
|
setBlogMiddlewares[blog] = sbm
|
||||||
searchRouter.Use(privateModeHandler...)
|
|
||||||
searchRouter.Use(cacheMiddleware)
|
|
||||||
handler := serveSearch(blog, searchPath)
|
|
||||||
searchRouter.Get("/", handler)
|
|
||||||
searchRouter.Post("/", handler)
|
|
||||||
searchResultPath := "/" + searchPlaceholder
|
|
||||||
resultHandler := serveSearchResults(blog, searchPath+searchResultPath)
|
|
||||||
searchRouter.Get(searchResultPath, resultHandler)
|
|
||||||
searchRouter.Get(searchResultPath+feedPath, resultHandler)
|
|
||||||
searchRouter.Get(searchResultPath+paginationPath, resultHandler)
|
|
||||||
searchRouters[blog] = searchRouter
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func buildDynamicRouter() (*chi.Mux, error) {
|
func buildDynamicRouter() (*chi.Mux, error) {
|
||||||
startTime := time.Now()
|
|
||||||
|
|
||||||
r := chi.NewRouter()
|
r := chi.NewRouter()
|
||||||
|
|
||||||
// Basic middleware
|
// Basic middleware
|
||||||
|
@ -321,7 +307,9 @@ func buildDynamicRouter() (*chi.Mux, error) {
|
||||||
r.With(privateModeHandler...).With(cacheMiddleware).Get("/s/{id:[0-9a-fA-F]+}", redirectToLongPath)
|
r.With(privateModeHandler...).With(cacheMiddleware).Get("/s/{id:[0-9a-fA-F]+}", redirectToLongPath)
|
||||||
|
|
||||||
for blog, blogConfig := range appConfig.Blogs {
|
for blog, blogConfig := range appConfig.Blogs {
|
||||||
blogPath := blogPath(blogConfig)
|
blogPath := blogPath(blog)
|
||||||
|
|
||||||
|
sbm := setBlogMiddlewares[blog]
|
||||||
|
|
||||||
// Sections
|
// Sections
|
||||||
r.Group(func(r chi.Router) {
|
r.Group(func(r chi.Router) {
|
||||||
|
@ -330,10 +318,15 @@ func buildDynamicRouter() (*chi.Mux, error) {
|
||||||
for _, section := range blogConfig.Sections {
|
for _, section := range blogConfig.Sections {
|
||||||
if section.Name != "" {
|
if section.Name != "" {
|
||||||
secPath := blogPath + "/" + section.Name
|
secPath := blogPath + "/" + section.Name
|
||||||
handler := serveSection(blog, secPath, section)
|
r.Group(func(r chi.Router) {
|
||||||
r.Get(secPath, handler)
|
r.Use(sbm, middleware.WithValue(indexConfigKey, &indexConfig{
|
||||||
r.Get(secPath+feedPath, handler)
|
path: secPath,
|
||||||
r.Get(secPath+paginationPath, handler)
|
section: section,
|
||||||
|
}))
|
||||||
|
r.Get(secPath, serveIndex)
|
||||||
|
r.Get(secPath+feedPath, serveIndex)
|
||||||
|
r.Get(secPath+paginationPath, serveIndex)
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
@ -349,13 +342,19 @@ func buildDynamicRouter() (*chi.Mux, error) {
|
||||||
r.Group(func(r chi.Router) {
|
r.Group(func(r chi.Router) {
|
||||||
r.Use(privateModeHandler...)
|
r.Use(privateModeHandler...)
|
||||||
r.Use(cacheMiddleware)
|
r.Use(cacheMiddleware)
|
||||||
r.Get(taxPath, serveTaxonomy(blog, taxonomy))
|
r.With(sbm, middleware.WithValue(taxonomyContextKey, taxonomy)).Get(taxPath, serveTaxonomy)
|
||||||
for _, tv := range taxValues {
|
for _, tv := range taxValues {
|
||||||
vPath := taxPath + "/" + urlize(tv)
|
vPath := taxPath + "/" + urlize(tv)
|
||||||
handler := serveTaxonomyValue(blog, vPath, taxonomy, tv)
|
r.Group(func(r chi.Router) {
|
||||||
r.Get(vPath, handler)
|
r.Use(sbm, middleware.WithValue(indexConfigKey, &indexConfig{
|
||||||
r.Get(vPath+feedPath, handler)
|
path: vPath,
|
||||||
r.Get(vPath+paginationPath, handler)
|
tax: taxonomy,
|
||||||
|
taxValue: tv,
|
||||||
|
}))
|
||||||
|
r.Get(vPath, serveIndex)
|
||||||
|
r.Get(vPath+feedPath, serveIndex)
|
||||||
|
r.Get(vPath+paginationPath, serveIndex)
|
||||||
|
})
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -367,101 +366,74 @@ func buildDynamicRouter() (*chi.Mux, error) {
|
||||||
r.Use(privateModeHandler...)
|
r.Use(privateModeHandler...)
|
||||||
r.Use(cacheMiddleware)
|
r.Use(cacheMiddleware)
|
||||||
photoPath := blogPath + blogConfig.Photos.Path
|
photoPath := blogPath + blogConfig.Photos.Path
|
||||||
handler := servePhotos(blog, photoPath)
|
r.Use(sbm, middleware.WithValue(indexConfigKey, &indexConfig{
|
||||||
r.Get(photoPath, handler)
|
path: photoPath,
|
||||||
r.Get(photoPath+feedPath, handler)
|
parameter: blogConfig.Photos.Parameter,
|
||||||
r.Get(photoPath+paginationPath, handler)
|
title: blogConfig.Photos.Title,
|
||||||
|
description: blogConfig.Photos.Description,
|
||||||
|
summaryTemplate: templatePhotosSummary,
|
||||||
|
}))
|
||||||
|
r.Get(photoPath, serveIndex)
|
||||||
|
r.Get(photoPath+feedPath, serveIndex)
|
||||||
|
r.Get(photoPath+paginationPath, serveIndex)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// Search
|
// Search
|
||||||
if blogConfig.Search != nil && blogConfig.Search.Enabled {
|
if blogConfig.Search != nil && blogConfig.Search.Enabled {
|
||||||
r.Mount(blogPath+blogConfig.Search.Path, searchRouters[blog])
|
searchPath := blogPath + blogConfig.Search.Path
|
||||||
|
r.With(sbm, middleware.WithValue(pathContextKey, searchPath)).Mount(searchPath, searchRouter)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Stats
|
// Stats
|
||||||
if blogConfig.BlogStats != nil && blogConfig.BlogStats.Enabled {
|
if blogConfig.BlogStats != nil && blogConfig.BlogStats.Enabled {
|
||||||
statsPath := blogPath + blogConfig.BlogStats.Path
|
statsPath := blogPath + blogConfig.BlogStats.Path
|
||||||
r.With(privateModeHandler...).With(cacheMiddleware).Get(statsPath, serveBlogStats(blog, statsPath))
|
r.With(privateModeHandler...).With(cacheMiddleware, sbm).Get(statsPath, serveBlogStats)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Year / month archives
|
// Date archives
|
||||||
dates, err := allPublishedDates(blog)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
r.Group(func(r chi.Router) {
|
r.Group(func(r chi.Router) {
|
||||||
r.Use(privateModeHandler...)
|
r.Use(privateModeHandler...)
|
||||||
r.Use(cacheMiddleware)
|
r.Use(cacheMiddleware, sbm)
|
||||||
already := map[string]bool{}
|
|
||||||
for _, d := range dates {
|
yearRegex := `/{year:x|\d\d\d\d}`
|
||||||
// Year
|
monthRegex := `/{month:x|\d\d}`
|
||||||
yearPath := blogPath + "/" + fmt.Sprintf("%0004d", d.year)
|
dayRegex := `/{day:\d\d}`
|
||||||
if !already[yearPath] {
|
|
||||||
yearHandler := serveDate(blog, yearPath, d.year, 0, 0)
|
yearPath := blogPath + yearRegex
|
||||||
r.Get(yearPath, yearHandler)
|
r.Get(yearPath, serveDate)
|
||||||
r.Get(yearPath+feedPath, yearHandler)
|
r.Get(yearPath+feedPath, serveDate)
|
||||||
r.Get(yearPath+paginationPath, yearHandler)
|
r.Get(yearPath+paginationPath, serveDate)
|
||||||
already[yearPath] = true
|
|
||||||
}
|
monthPath := yearPath + monthRegex
|
||||||
// Specific month
|
r.Get(monthPath, serveDate)
|
||||||
monthPath := yearPath + "/" + fmt.Sprintf("%02d", d.month)
|
r.Get(monthPath+feedPath, serveDate)
|
||||||
if !already[monthPath] {
|
r.Get(monthPath+paginationPath, serveDate)
|
||||||
monthHandler := serveDate(blog, monthPath, d.year, d.month, 0)
|
|
||||||
r.Get(monthPath, monthHandler)
|
dayPath := monthPath + dayRegex
|
||||||
r.Get(monthPath+feedPath, monthHandler)
|
r.Get(dayPath, serveDate)
|
||||||
r.Get(monthPath+paginationPath, monthHandler)
|
r.Get(dayPath+feedPath, serveDate)
|
||||||
already[monthPath] = true
|
r.Get(dayPath+paginationPath, serveDate)
|
||||||
}
|
|
||||||
// Specific day
|
|
||||||
dayPath := monthPath + "/" + fmt.Sprintf("%02d", d.day)
|
|
||||||
if !already[dayPath] {
|
|
||||||
dayHandler := serveDate(blog, monthPath, d.year, d.month, d.day)
|
|
||||||
r.Get(dayPath, dayHandler)
|
|
||||||
r.Get(dayPath+feedPath, dayHandler)
|
|
||||||
r.Get(dayPath+paginationPath, dayHandler)
|
|
||||||
already[dayPath] = true
|
|
||||||
}
|
|
||||||
// Generic month
|
|
||||||
genericMonthPath := blogPath + "/x/" + fmt.Sprintf("%02d", d.month)
|
|
||||||
if !already[genericMonthPath] {
|
|
||||||
genericMonthHandler := serveDate(blog, genericMonthPath, 0, d.month, 0)
|
|
||||||
r.Get(genericMonthPath, genericMonthHandler)
|
|
||||||
r.Get(genericMonthPath+feedPath, genericMonthHandler)
|
|
||||||
r.Get(genericMonthPath+paginationPath, genericMonthHandler)
|
|
||||||
already[genericMonthPath] = true
|
|
||||||
}
|
|
||||||
// Specific day
|
|
||||||
genericMonthDayPath := genericMonthPath + "/" + fmt.Sprintf("%02d", d.day)
|
|
||||||
if !already[genericMonthDayPath] {
|
|
||||||
genericMonthDayHandler := serveDate(blog, genericMonthDayPath, 0, d.month, d.day)
|
|
||||||
r.Get(genericMonthDayPath, genericMonthDayHandler)
|
|
||||||
r.Get(genericMonthDayPath+feedPath, genericMonthDayHandler)
|
|
||||||
r.Get(genericMonthDayPath+paginationPath, genericMonthDayHandler)
|
|
||||||
already[genericMonthDayPath] = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
|
|
||||||
// Blog
|
// Blog
|
||||||
if !blogConfig.PostAsHome {
|
if !blogConfig.PostAsHome {
|
||||||
r.Group(func(r chi.Router) {
|
r.Group(func(r chi.Router) {
|
||||||
r.Use(privateModeHandler...)
|
r.Use(privateModeHandler...)
|
||||||
r.Use(cacheMiddleware)
|
r.Use(cacheMiddleware, sbm)
|
||||||
handler := serveHome(blog, blogPath)
|
r.Get(blogConfig.Path, serveHome)
|
||||||
r.Get(blogConfig.Path, handler)
|
r.Get(blogConfig.Path+feedPath, serveHome)
|
||||||
r.Get(blogConfig.Path+feedPath, handler)
|
r.Get(blogPath+paginationPath, serveHome)
|
||||||
r.Get(blogPath+paginationPath, handler)
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// Custom pages
|
// Custom pages
|
||||||
for _, cp := range blogConfig.CustomPages {
|
for _, cp := range blogConfig.CustomPages {
|
||||||
handler := serveCustomPage(blogConfig, cp)
|
scp := middleware.WithValue(customPageContextKey, cp)
|
||||||
if cp.Cache {
|
if cp.Cache {
|
||||||
r.With(privateModeHandler...).With(cacheMiddleware).Get(cp.Path, handler)
|
r.With(privateModeHandler...).With(cacheMiddleware, sbm, scp).Get(cp.Path, serveCustomPage)
|
||||||
} else {
|
} else {
|
||||||
r.With(privateModeHandler...).Get(cp.Path, handler)
|
r.With(privateModeHandler...).With(sbm, scp).Get(cp.Path, serveCustomPage)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -471,15 +443,16 @@ func buildDynamicRouter() (*chi.Mux, error) {
|
||||||
if randomPath == "" {
|
if randomPath == "" {
|
||||||
randomPath = "/random"
|
randomPath = "/random"
|
||||||
}
|
}
|
||||||
r.With(privateModeHandler...).Get(blogPath+randomPath, redirectToRandomPost(blog))
|
r.With(privateModeHandler...).With(sbm).Get(blogPath+randomPath, redirectToRandomPost)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Editor
|
// Editor
|
||||||
r.Mount(blogPath+"/editor", editorRouters[blog])
|
r.With(sbm).Mount(blogPath+"/editor", editorRouter)
|
||||||
|
|
||||||
// Comments
|
// Comments
|
||||||
if commentsConfig := blogConfig.Comments; commentsConfig != nil && commentsConfig.Enabled {
|
if commentsConfig := blogConfig.Comments; commentsConfig != nil && commentsConfig.Enabled {
|
||||||
r.Mount(blogPath+"/comment", commentRouters[blog])
|
commentsPath := blogPath + "/comment"
|
||||||
|
r.With(sbm, middleware.WithValue(pathContextKey, commentsPath)).Mount(commentsPath, commentsRouter)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -500,19 +473,20 @@ func buildDynamicRouter() (*chi.Mux, error) {
|
||||||
serveError(rw, r, "", http.StatusMethodNotAllowed)
|
serveError(rw, r, "", http.StatusMethodNotAllowed)
|
||||||
})
|
})
|
||||||
|
|
||||||
log.Println("Building handler took", time.Since(startTime))
|
|
||||||
|
|
||||||
return r, nil
|
return r, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func blogPath(cb *configBlog) string {
|
func blogPath(blog string) string {
|
||||||
blogPath := cb.Path
|
blogPath := appConfig.Blogs[blog].Path
|
||||||
if blogPath == "/" {
|
if blogPath == "/" {
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
return blogPath
|
return blogPath
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const blogContextKey requestContextKey = "blog"
|
||||||
|
const pathContextKey requestContextKey = "httpPath"
|
||||||
|
|
||||||
var cspDomains = ""
|
var cspDomains = ""
|
||||||
|
|
||||||
func refreshCSPDomains() {
|
func refreshCSPDomains() {
|
||||||
|
|
|
@ -112,50 +112,48 @@ func (p *notificationsPaginationAdapter) Slice(offset, length int, data interfac
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
func notificationsAdmin(notificationPath string) func(http.ResponseWriter, *http.Request) {
|
func notificationsAdmin(w http.ResponseWriter, r *http.Request) {
|
||||||
return func(w http.ResponseWriter, r *http.Request) {
|
// Adapter
|
||||||
// Adapter
|
pageNoString := chi.URLParam(r, "page")
|
||||||
pageNoString := chi.URLParam(r, "page")
|
pageNo, _ := strconv.Atoi(pageNoString)
|
||||||
pageNo, _ := strconv.Atoi(pageNoString)
|
p := paginator.New(¬ificationsPaginationAdapter{config: ¬ificationsRequestConfig{}}, 10)
|
||||||
p := paginator.New(¬ificationsPaginationAdapter{config: ¬ificationsRequestConfig{}}, 10)
|
p.SetPage(pageNo)
|
||||||
p.SetPage(pageNo)
|
var notifications []*notification
|
||||||
var notifications []*notification
|
err := p.Results(¬ifications)
|
||||||
err := p.Results(¬ifications)
|
if err != nil {
|
||||||
if err != nil {
|
serveError(w, r, err.Error(), http.StatusInternalServerError)
|
||||||
serveError(w, r, err.Error(), http.StatusInternalServerError)
|
return
|
||||||
return
|
|
||||||
}
|
|
||||||
// Navigation
|
|
||||||
var hasPrev, hasNext bool
|
|
||||||
var prevPage, nextPage int
|
|
||||||
var prevPath, nextPath string
|
|
||||||
hasPrev, _ = p.HasPrev()
|
|
||||||
if hasPrev {
|
|
||||||
prevPage, _ = p.PrevPage()
|
|
||||||
} else {
|
|
||||||
prevPage, _ = p.Page()
|
|
||||||
}
|
|
||||||
if prevPage < 2 {
|
|
||||||
prevPath = notificationPath
|
|
||||||
} else {
|
|
||||||
prevPath = fmt.Sprintf("%s/page/%d", notificationPath, prevPage)
|
|
||||||
}
|
|
||||||
hasNext, _ = p.HasNext()
|
|
||||||
if hasNext {
|
|
||||||
nextPage, _ = p.NextPage()
|
|
||||||
} else {
|
|
||||||
nextPage, _ = p.Page()
|
|
||||||
}
|
|
||||||
nextPath = fmt.Sprintf("%s/page/%d", notificationPath, nextPage)
|
|
||||||
// Render
|
|
||||||
render(w, r, templateNotificationsAdmin, &renderData{
|
|
||||||
Data: map[string]interface{}{
|
|
||||||
"Notifications": notifications,
|
|
||||||
"HasPrev": hasPrev,
|
|
||||||
"HasNext": hasNext,
|
|
||||||
"Prev": slashIfEmpty(prevPath),
|
|
||||||
"Next": slashIfEmpty(nextPath),
|
|
||||||
},
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
// Navigation
|
||||||
|
var hasPrev, hasNext bool
|
||||||
|
var prevPage, nextPage int
|
||||||
|
var prevPath, nextPath string
|
||||||
|
hasPrev, _ = p.HasPrev()
|
||||||
|
if hasPrev {
|
||||||
|
prevPage, _ = p.PrevPage()
|
||||||
|
} else {
|
||||||
|
prevPage, _ = p.Page()
|
||||||
|
}
|
||||||
|
if prevPage < 2 {
|
||||||
|
prevPath = notificationsPath
|
||||||
|
} else {
|
||||||
|
prevPath = fmt.Sprintf("%s/page/%d", notificationsPath, prevPage)
|
||||||
|
}
|
||||||
|
hasNext, _ = p.HasNext()
|
||||||
|
if hasNext {
|
||||||
|
nextPage, _ = p.NextPage()
|
||||||
|
} else {
|
||||||
|
nextPage, _ = p.Page()
|
||||||
|
}
|
||||||
|
nextPath = fmt.Sprintf("%s/page/%d", notificationsPath, nextPage)
|
||||||
|
// Render
|
||||||
|
render(w, r, templateNotificationsAdmin, &renderData{
|
||||||
|
Data: map[string]interface{}{
|
||||||
|
"Notifications": notifications,
|
||||||
|
"HasPrev": hasPrev,
|
||||||
|
"HasNext": hasNext,
|
||||||
|
"Prev": slashIfEmpty(prevPath),
|
||||||
|
"Next": slashIfEmpty(nextPath),
|
||||||
|
},
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
309
posts.go
309
posts.go
|
@ -1,6 +1,7 @@
|
||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"html/template"
|
"html/template"
|
||||||
|
@ -71,15 +72,13 @@ func servePost(w http.ResponseWriter, r *http.Request) {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func redirectToRandomPost(blog string) func(http.ResponseWriter, *http.Request) {
|
func redirectToRandomPost(rw http.ResponseWriter, r *http.Request) {
|
||||||
return func(rw http.ResponseWriter, r *http.Request) {
|
randomPath, err := getRandomPostPath(r.Context().Value(blogContextKey).(string))
|
||||||
randomPath, err := getRandomPostPath(blog)
|
if err != nil {
|
||||||
if err != nil {
|
serveError(rw, r, err.Error(), http.StatusInternalServerError)
|
||||||
serveError(rw, r, err.Error(), http.StatusInternalServerError)
|
return
|
||||||
return
|
|
||||||
}
|
|
||||||
http.Redirect(rw, r, randomPath, http.StatusFound)
|
|
||||||
}
|
}
|
||||||
|
http.Redirect(rw, r, randomPath, http.StatusFound)
|
||||||
}
|
}
|
||||||
|
|
||||||
type postPaginationAdapter struct {
|
type postPaginationAdapter struct {
|
||||||
|
@ -105,77 +104,60 @@ func (p *postPaginationAdapter) Slice(offset, length int, data interface{}) erro
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
func serveHome(blog string, path string) func(w http.ResponseWriter, r *http.Request) {
|
func serveHome(w http.ResponseWriter, r *http.Request) {
|
||||||
return func(w http.ResponseWriter, r *http.Request) {
|
blog := r.Context().Value(blogContextKey).(string)
|
||||||
if asRequest, ok := r.Context().Value(asRequestKey).(bool); ok && asRequest {
|
if asRequest, ok := r.Context().Value(asRequestKey).(bool); ok && asRequest {
|
||||||
appConfig.Blogs[blog].serveActivityStreams(blog, w, r)
|
appConfig.Blogs[blog].serveActivityStreams(blog, w, r)
|
||||||
return
|
return
|
||||||
}
|
|
||||||
serveIndex(&indexConfig{
|
|
||||||
blog: blog,
|
|
||||||
path: path,
|
|
||||||
})(w, r)
|
|
||||||
}
|
}
|
||||||
|
serveIndex(w, r.WithContext(context.WithValue(r.Context(), indexConfigKey, &indexConfig{
|
||||||
|
path: blogPath(blog),
|
||||||
|
})))
|
||||||
}
|
}
|
||||||
|
|
||||||
func serveSection(blog string, path string, section *section) func(w http.ResponseWriter, r *http.Request) {
|
func serveDate(w http.ResponseWriter, r *http.Request) {
|
||||||
return serveIndex(&indexConfig{
|
var year, month, day int
|
||||||
blog: blog,
|
if ys := chi.URLParam(r, "year"); ys != "" && ys != "x" {
|
||||||
path: path,
|
year, _ = strconv.Atoi(ys)
|
||||||
section: section,
|
}
|
||||||
})
|
if ms := chi.URLParam(r, "month"); ms != "" && ms != "x" {
|
||||||
}
|
month, _ = strconv.Atoi(ms)
|
||||||
|
}
|
||||||
func serveTaxonomyValue(blog string, path string, tax *taxonomy, value string) func(w http.ResponseWriter, r *http.Request) {
|
if ds := chi.URLParam(r, "day"); ds != "" {
|
||||||
return serveIndex(&indexConfig{
|
day, _ = strconv.Atoi(ds)
|
||||||
blog: blog,
|
}
|
||||||
path: path,
|
if year == 0 && month == 0 && day == 0 {
|
||||||
tax: tax,
|
serve404(w, r)
|
||||||
taxValue: value,
|
return
|
||||||
})
|
}
|
||||||
}
|
var title, dPath strings.Builder
|
||||||
|
dPath.WriteString(blogPath(r.Context().Value(blogContextKey).(string)) + "/")
|
||||||
func servePhotos(blog string, path string) func(w http.ResponseWriter, r *http.Request) {
|
|
||||||
return serveIndex(&indexConfig{
|
|
||||||
blog: blog,
|
|
||||||
path: path,
|
|
||||||
parameter: appConfig.Blogs[blog].Photos.Parameter,
|
|
||||||
title: appConfig.Blogs[blog].Photos.Title,
|
|
||||||
description: appConfig.Blogs[blog].Photos.Description,
|
|
||||||
summaryTemplate: templatePhotosSummary,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func serveSearchResults(blog string, path string) func(w http.ResponseWriter, r *http.Request) {
|
|
||||||
return serveIndex(&indexConfig{
|
|
||||||
blog: blog,
|
|
||||||
path: path,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func serveDate(blog string, path string, year, month, day int) func(w http.ResponseWriter, r *http.Request) {
|
|
||||||
var title strings.Builder
|
|
||||||
if year != 0 {
|
if year != 0 {
|
||||||
title.WriteString(fmt.Sprintf("%0004d", year))
|
ys := fmt.Sprintf("%0004d", year)
|
||||||
|
title.WriteString(ys)
|
||||||
|
dPath.WriteString(ys)
|
||||||
} else {
|
} else {
|
||||||
title.WriteString("XXXX")
|
title.WriteString("XXXX")
|
||||||
|
dPath.WriteString("x")
|
||||||
}
|
}
|
||||||
if month != 0 {
|
if month != 0 {
|
||||||
title.WriteString(fmt.Sprintf("-%02d", month))
|
title.WriteString(fmt.Sprintf("-%02d", month))
|
||||||
|
dPath.WriteString(fmt.Sprintf("/%02d", month))
|
||||||
} else if day != 0 {
|
} else if day != 0 {
|
||||||
title.WriteString("-XX")
|
title.WriteString("-XX")
|
||||||
|
dPath.WriteString("/x")
|
||||||
}
|
}
|
||||||
if day != 0 {
|
if day != 0 {
|
||||||
title.WriteString(fmt.Sprintf("-%02d", day))
|
title.WriteString(fmt.Sprintf("-%02d", day))
|
||||||
|
dPath.WriteString(fmt.Sprintf("/%02d", day))
|
||||||
}
|
}
|
||||||
return serveIndex(&indexConfig{
|
serveIndex(w, r.WithContext(context.WithValue(r.Context(), indexConfigKey, &indexConfig{
|
||||||
blog: blog,
|
path: dPath.String(),
|
||||||
path: path,
|
|
||||||
year: year,
|
year: year,
|
||||||
month: month,
|
month: month,
|
||||||
day: day,
|
day: day,
|
||||||
title: title.String(),
|
title: title.String(),
|
||||||
})
|
})))
|
||||||
}
|
}
|
||||||
|
|
||||||
type indexConfig struct {
|
type indexConfig struct {
|
||||||
|
@ -191,106 +173,111 @@ type indexConfig struct {
|
||||||
summaryTemplate string
|
summaryTemplate string
|
||||||
}
|
}
|
||||||
|
|
||||||
func serveIndex(ic *indexConfig) func(w http.ResponseWriter, r *http.Request) {
|
const indexConfigKey requestContextKey = "indexConfig"
|
||||||
return func(w http.ResponseWriter, r *http.Request) {
|
|
||||||
search := chi.URLParam(r, "search")
|
func serveIndex(w http.ResponseWriter, r *http.Request) {
|
||||||
if search != "" {
|
ic := r.Context().Value(indexConfigKey).(*indexConfig)
|
||||||
search = searchDecode(search)
|
blog := ic.blog
|
||||||
}
|
if blog == "" {
|
||||||
pageNoString := chi.URLParam(r, "page")
|
blog, _ = r.Context().Value(blogContextKey).(string)
|
||||||
pageNo, _ := strconv.Atoi(pageNoString)
|
|
||||||
var sections []string
|
|
||||||
if ic.section != nil {
|
|
||||||
sections = []string{ic.section.Name}
|
|
||||||
} else {
|
|
||||||
for sectionKey := range appConfig.Blogs[ic.blog].Sections {
|
|
||||||
sections = append(sections, sectionKey)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
p := paginator.New(&postPaginationAdapter{config: &postsRequestConfig{
|
|
||||||
blog: ic.blog,
|
|
||||||
sections: sections,
|
|
||||||
taxonomy: ic.tax,
|
|
||||||
taxonomyValue: ic.taxValue,
|
|
||||||
parameter: ic.parameter,
|
|
||||||
search: search,
|
|
||||||
publishedYear: ic.year,
|
|
||||||
publishedMonth: ic.month,
|
|
||||||
publishedDay: ic.day,
|
|
||||||
status: statusPublished,
|
|
||||||
}}, appConfig.Blogs[ic.blog].Pagination)
|
|
||||||
p.SetPage(pageNo)
|
|
||||||
var posts []*post
|
|
||||||
t := servertiming.FromContext(r.Context()).NewMetric("gp").Start()
|
|
||||||
err := p.Results(&posts)
|
|
||||||
t.Stop()
|
|
||||||
if err != nil {
|
|
||||||
serveError(w, r, err.Error(), http.StatusInternalServerError)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
// Meta
|
|
||||||
title := ic.title
|
|
||||||
description := ic.description
|
|
||||||
if ic.tax != nil {
|
|
||||||
title = fmt.Sprintf("%s: %s", ic.tax.Title, ic.taxValue)
|
|
||||||
} else if ic.section != nil {
|
|
||||||
title = ic.section.Title
|
|
||||||
description = ic.section.Description
|
|
||||||
} else if search != "" {
|
|
||||||
title = fmt.Sprintf("%s: %s", appConfig.Blogs[ic.blog].Search.Title, search)
|
|
||||||
}
|
|
||||||
// Clean title
|
|
||||||
title = bluemonday.StrictPolicy().Sanitize(title)
|
|
||||||
// Check if feed
|
|
||||||
if ft := feedType(chi.URLParam(r, "feed")); ft != noFeed {
|
|
||||||
generateFeed(ic.blog, ft, w, r, posts, title, description)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
// Path
|
|
||||||
path := ic.path
|
|
||||||
if strings.Contains(path, searchPlaceholder) {
|
|
||||||
path = strings.ReplaceAll(path, searchPlaceholder, searchEncode(search))
|
|
||||||
}
|
|
||||||
// Navigation
|
|
||||||
var hasPrev, hasNext bool
|
|
||||||
var prevPage, nextPage int
|
|
||||||
var prevPath, nextPath string
|
|
||||||
hasPrev, _ = p.HasPrev()
|
|
||||||
if hasPrev {
|
|
||||||
prevPage, _ = p.PrevPage()
|
|
||||||
} else {
|
|
||||||
prevPage, _ = p.Page()
|
|
||||||
}
|
|
||||||
if prevPage < 2 {
|
|
||||||
prevPath = path
|
|
||||||
} else {
|
|
||||||
prevPath = fmt.Sprintf("%s/page/%d", path, prevPage)
|
|
||||||
}
|
|
||||||
hasNext, _ = p.HasNext()
|
|
||||||
if hasNext {
|
|
||||||
nextPage, _ = p.NextPage()
|
|
||||||
} else {
|
|
||||||
nextPage, _ = p.Page()
|
|
||||||
}
|
|
||||||
nextPath = fmt.Sprintf("%s/page/%d", path, nextPage)
|
|
||||||
summaryTemplate := ic.summaryTemplate
|
|
||||||
if summaryTemplate == "" {
|
|
||||||
summaryTemplate = templateSummary
|
|
||||||
}
|
|
||||||
render(w, r, templateIndex, &renderData{
|
|
||||||
BlogString: ic.blog,
|
|
||||||
Canonical: appConfig.Server.PublicAddress + path,
|
|
||||||
Data: map[string]interface{}{
|
|
||||||
"Title": title,
|
|
||||||
"Description": description,
|
|
||||||
"Posts": posts,
|
|
||||||
"HasPrev": hasPrev,
|
|
||||||
"HasNext": hasNext,
|
|
||||||
"First": slashIfEmpty(path),
|
|
||||||
"Prev": slashIfEmpty(prevPath),
|
|
||||||
"Next": slashIfEmpty(nextPath),
|
|
||||||
"SummaryTemplate": summaryTemplate,
|
|
||||||
},
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
search := chi.URLParam(r, "search")
|
||||||
|
if search != "" {
|
||||||
|
search = searchDecode(search)
|
||||||
|
}
|
||||||
|
pageNoString := chi.URLParam(r, "page")
|
||||||
|
pageNo, _ := strconv.Atoi(pageNoString)
|
||||||
|
var sections []string
|
||||||
|
if ic.section != nil {
|
||||||
|
sections = []string{ic.section.Name}
|
||||||
|
} else {
|
||||||
|
for sectionKey := range appConfig.Blogs[blog].Sections {
|
||||||
|
sections = append(sections, sectionKey)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
p := paginator.New(&postPaginationAdapter{config: &postsRequestConfig{
|
||||||
|
blog: blog,
|
||||||
|
sections: sections,
|
||||||
|
taxonomy: ic.tax,
|
||||||
|
taxonomyValue: ic.taxValue,
|
||||||
|
parameter: ic.parameter,
|
||||||
|
search: search,
|
||||||
|
publishedYear: ic.year,
|
||||||
|
publishedMonth: ic.month,
|
||||||
|
publishedDay: ic.day,
|
||||||
|
status: statusPublished,
|
||||||
|
}}, appConfig.Blogs[blog].Pagination)
|
||||||
|
p.SetPage(pageNo)
|
||||||
|
var posts []*post
|
||||||
|
t := servertiming.FromContext(r.Context()).NewMetric("gp").Start()
|
||||||
|
err := p.Results(&posts)
|
||||||
|
t.Stop()
|
||||||
|
if err != nil {
|
||||||
|
serveError(w, r, err.Error(), http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// Meta
|
||||||
|
title := ic.title
|
||||||
|
description := ic.description
|
||||||
|
if ic.tax != nil {
|
||||||
|
title = fmt.Sprintf("%s: %s", ic.tax.Title, ic.taxValue)
|
||||||
|
} else if ic.section != nil {
|
||||||
|
title = ic.section.Title
|
||||||
|
description = ic.section.Description
|
||||||
|
} else if search != "" {
|
||||||
|
title = fmt.Sprintf("%s: %s", appConfig.Blogs[blog].Search.Title, search)
|
||||||
|
}
|
||||||
|
// Clean title
|
||||||
|
title = bluemonday.StrictPolicy().Sanitize(title)
|
||||||
|
// Check if feed
|
||||||
|
if ft := feedType(chi.URLParam(r, "feed")); ft != noFeed {
|
||||||
|
generateFeed(blog, ft, w, r, posts, title, description)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// Path
|
||||||
|
path := ic.path
|
||||||
|
if strings.Contains(path, searchPlaceholder) {
|
||||||
|
path = strings.ReplaceAll(path, searchPlaceholder, searchEncode(search))
|
||||||
|
}
|
||||||
|
// Navigation
|
||||||
|
var hasPrev, hasNext bool
|
||||||
|
var prevPage, nextPage int
|
||||||
|
var prevPath, nextPath string
|
||||||
|
hasPrev, _ = p.HasPrev()
|
||||||
|
if hasPrev {
|
||||||
|
prevPage, _ = p.PrevPage()
|
||||||
|
} else {
|
||||||
|
prevPage, _ = p.Page()
|
||||||
|
}
|
||||||
|
if prevPage < 2 {
|
||||||
|
prevPath = path
|
||||||
|
} else {
|
||||||
|
prevPath = fmt.Sprintf("%s/page/%d", path, prevPage)
|
||||||
|
}
|
||||||
|
hasNext, _ = p.HasNext()
|
||||||
|
if hasNext {
|
||||||
|
nextPage, _ = p.NextPage()
|
||||||
|
} else {
|
||||||
|
nextPage, _ = p.Page()
|
||||||
|
}
|
||||||
|
nextPath = fmt.Sprintf("%s/page/%d", path, nextPage)
|
||||||
|
summaryTemplate := ic.summaryTemplate
|
||||||
|
if summaryTemplate == "" {
|
||||||
|
summaryTemplate = templateSummary
|
||||||
|
}
|
||||||
|
render(w, r, templateIndex, &renderData{
|
||||||
|
BlogString: blog,
|
||||||
|
Canonical: appConfig.Server.PublicAddress + path,
|
||||||
|
Data: map[string]interface{}{
|
||||||
|
"Title": title,
|
||||||
|
"Description": description,
|
||||||
|
"Posts": posts,
|
||||||
|
"HasPrev": hasPrev,
|
||||||
|
"HasNext": hasNext,
|
||||||
|
"First": slashIfEmpty(path),
|
||||||
|
"Prev": slashIfEmpty(prevPath),
|
||||||
|
"Next": slashIfEmpty(nextPath),
|
||||||
|
"SummaryTemplate": summaryTemplate,
|
||||||
|
},
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
37
search.go
37
search.go
|
@ -1,6 +1,7 @@
|
||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"encoding/base64"
|
"encoding/base64"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/url"
|
"net/url"
|
||||||
|
@ -10,22 +11,28 @@ import (
|
||||||
|
|
||||||
const searchPlaceholder = "{search}"
|
const searchPlaceholder = "{search}"
|
||||||
|
|
||||||
func serveSearch(blog string, servePath string) func(w http.ResponseWriter, r *http.Request) {
|
func serveSearch(w http.ResponseWriter, r *http.Request) {
|
||||||
return func(w http.ResponseWriter, r *http.Request) {
|
blog := r.Context().Value(blogContextKey).(string)
|
||||||
err := r.ParseForm()
|
servePath := r.Context().Value(pathContextKey).(string)
|
||||||
if err != nil {
|
err := r.ParseForm()
|
||||||
serveError(w, r, err.Error(), http.StatusBadRequest)
|
if err != nil {
|
||||||
return
|
serveError(w, r, err.Error(), http.StatusBadRequest)
|
||||||
}
|
return
|
||||||
if q := r.Form.Get("q"); q != "" {
|
|
||||||
http.Redirect(w, r, path.Join(servePath, searchEncode(q)), http.StatusFound)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
render(w, r, templateSearch, &renderData{
|
|
||||||
BlogString: blog,
|
|
||||||
Canonical: appConfig.Server.PublicAddress + servePath,
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
if q := r.Form.Get("q"); q != "" {
|
||||||
|
http.Redirect(w, r, path.Join(servePath, searchEncode(q)), http.StatusFound)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
render(w, r, templateSearch, &renderData{
|
||||||
|
BlogString: blog,
|
||||||
|
Canonical: appConfig.Server.PublicAddress + servePath,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func serveSearchResult(w http.ResponseWriter, r *http.Request) {
|
||||||
|
serveIndex(w, r.WithContext(context.WithValue(r.Context(), indexConfigKey, &indexConfig{
|
||||||
|
path: r.Context().Value(pathContextKey).(string) + "/" + searchPlaceholder,
|
||||||
|
})))
|
||||||
}
|
}
|
||||||
|
|
||||||
func searchEncode(search string) string {
|
func searchEncode(search string) string {
|
||||||
|
|
|
@ -2,20 +2,22 @@ package main
|
||||||
|
|
||||||
import "net/http"
|
import "net/http"
|
||||||
|
|
||||||
func serveTaxonomy(blog string, tax *taxonomy) func(w http.ResponseWriter, r *http.Request) {
|
const taxonomyContextKey = "taxonomy"
|
||||||
return func(w http.ResponseWriter, r *http.Request) {
|
|
||||||
allValues, err := allTaxonomyValues(blog, tax.Name)
|
func serveTaxonomy(w http.ResponseWriter, r *http.Request) {
|
||||||
if err != nil {
|
blog := r.Context().Value(blogContextKey).(string)
|
||||||
serveError(w, r, err.Error(), http.StatusInternalServerError)
|
tax := r.Context().Value(taxonomyContextKey).(*taxonomy)
|
||||||
return
|
allValues, err := allTaxonomyValues(blog, tax.Name)
|
||||||
}
|
if err != nil {
|
||||||
render(w, r, templateTaxonomy, &renderData{
|
serveError(w, r, err.Error(), http.StatusInternalServerError)
|
||||||
BlogString: blog,
|
return
|
||||||
Canonical: appConfig.Server.PublicAddress + r.URL.Path,
|
|
||||||
Data: map[string]interface{}{
|
|
||||||
"Taxonomy": tax,
|
|
||||||
"ValueGroups": groupStrings(allValues),
|
|
||||||
},
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
render(w, r, templateTaxonomy, &renderData{
|
||||||
|
BlogString: blog,
|
||||||
|
Canonical: appConfig.Server.PublicAddress + r.URL.Path,
|
||||||
|
Data: map[string]interface{}{
|
||||||
|
"Taxonomy": tax,
|
||||||
|
"ValueGroups": groupStrings(allValues),
|
||||||
|
},
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
4
tor.go
4
tor.go
|
@ -41,7 +41,7 @@ func startOnionService(h http.Handler) error {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
pemEncoded := pem.EncodeToMemory(&pem.Block{Type: "PRIVATE KEY", Bytes: x509Encoded})
|
pemEncoded := pem.EncodeToMemory(&pem.Block{Type: "PRIVATE KEY", Bytes: x509Encoded})
|
||||||
os.WriteFile(torKeyPath, pemEncoded, os.ModePerm)
|
_ = os.WriteFile(torKeyPath, pemEncoded, os.ModePerm)
|
||||||
} else {
|
} else {
|
||||||
d, _ := os.ReadFile(torKeyPath)
|
d, _ := os.ReadFile(torKeyPath)
|
||||||
block, _ := pem.Decode(d)
|
block, _ := pem.Decode(d)
|
||||||
|
@ -53,7 +53,7 @@ func startOnionService(h http.Handler) error {
|
||||||
}
|
}
|
||||||
// Start tor with default config (can set start conf's DebugWriter to os.Stdout for debug logs)
|
// Start tor with default config (can set start conf's DebugWriter to os.Stdout for debug logs)
|
||||||
log.Println("Starting and registering onion service, please wait a couple of minutes...")
|
log.Println("Starting and registering onion service, please wait a couple of minutes...")
|
||||||
t, err := tor.Start(nil, &tor.StartConf{
|
t, err := tor.Start(context.Background(), &tor.StartConf{
|
||||||
TempDataDirBase: os.TempDir(),
|
TempDataDirBase: os.TempDir(),
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
Loading…
Reference in New Issue