mirror of
https://github.com/osmarks/mycorrhiza.git
synced 2025-12-10 10:28:08 +00:00
Move all web-related stuff to a new module
This commit is contained in:
44
web/http_admin.go
Normal file
44
web/http_admin.go
Normal file
@@ -0,0 +1,44 @@
|
||||
package web
|
||||
|
||||
import (
|
||||
"log"
|
||||
"net/http"
|
||||
|
||||
"github.com/bouncepaw/mycorrhiza/cfg"
|
||||
"github.com/bouncepaw/mycorrhiza/user"
|
||||
"github.com/bouncepaw/mycorrhiza/util"
|
||||
"github.com/bouncepaw/mycorrhiza/views"
|
||||
)
|
||||
|
||||
// InitAdmin sets up /admin routes if auth is used. Call it after you have decided if you want to use auth.
|
||||
func InitAdmin() {
|
||||
if user.AuthUsed {
|
||||
http.HandleFunc("/admin", HandlerAdmin)
|
||||
http.HandleFunc("/admin/shutdown", HandlerAdminShutdown)
|
||||
http.HandleFunc("/admin/reindex-users", HandlerAdminReindexUsers)
|
||||
}
|
||||
}
|
||||
|
||||
func HandlerAdmin(w http.ResponseWriter, rq *http.Request) {
|
||||
util.PrepareRq(rq)
|
||||
if user.CanProceed(rq, "admin") {
|
||||
w.Header().Set("Content-Type", "text/html;charset=utf-8")
|
||||
w.WriteHeader(http.StatusOK)
|
||||
w.Write([]byte(views.BaseHTML("Admin panel", views.AdminPanelHTML(), user.FromRequest(rq))))
|
||||
}
|
||||
}
|
||||
|
||||
func HandlerAdminShutdown(w http.ResponseWriter, rq *http.Request) {
|
||||
util.PrepareRq(rq)
|
||||
if user.CanProceed(rq, "admin/shutdown") && rq.Method == "POST" {
|
||||
log.Fatal("An admin commanded the wiki to shutdown")
|
||||
}
|
||||
}
|
||||
|
||||
func HandlerAdminReindexUsers(w http.ResponseWriter, rq *http.Request) {
|
||||
util.PrepareRq(rq)
|
||||
if user.CanProceed(rq, "admin") && rq.Method == "POST" {
|
||||
user.ReadUsersFromFilesystem()
|
||||
http.Redirect(w, rq, "/hypha/"+cfg.UserHypha, http.StatusSeeOther)
|
||||
}
|
||||
}
|
||||
95
web/http_auth.go
Normal file
95
web/http_auth.go
Normal file
@@ -0,0 +1,95 @@
|
||||
package web
|
||||
|
||||
import (
|
||||
"github.com/bouncepaw/mycorrhiza/cfg"
|
||||
"io"
|
||||
"log"
|
||||
"net/http"
|
||||
|
||||
"github.com/bouncepaw/mycorrhiza/user"
|
||||
"github.com/bouncepaw/mycorrhiza/util"
|
||||
"github.com/bouncepaw/mycorrhiza/views"
|
||||
)
|
||||
|
||||
func init() {
|
||||
http.HandleFunc("/register", handlerRegister)
|
||||
http.HandleFunc("/login", handlerLogin)
|
||||
http.HandleFunc("/login-data", handlerLoginData)
|
||||
http.HandleFunc("/logout", handlerLogout)
|
||||
http.HandleFunc("/logout-confirm", handlerLogoutConfirm)
|
||||
}
|
||||
|
||||
func handlerRegister(w http.ResponseWriter, rq *http.Request) {
|
||||
util.PrepareRq(rq)
|
||||
if !cfg.UseRegistration {
|
||||
w.WriteHeader(http.StatusForbidden)
|
||||
}
|
||||
if rq.Method == http.MethodGet {
|
||||
io.WriteString(
|
||||
w,
|
||||
views.BaseHTML(
|
||||
"Register",
|
||||
views.RegisterHTML(rq),
|
||||
user.FromRequest(rq),
|
||||
),
|
||||
)
|
||||
} else if rq.Method == http.MethodPost {
|
||||
var (
|
||||
username = rq.PostFormValue("username")
|
||||
password = rq.PostFormValue("password")
|
||||
err = user.Register(username, password)
|
||||
)
|
||||
if err != nil {
|
||||
io.WriteString(w, err.Error())
|
||||
} else {
|
||||
user.LoginDataHTTP(w, rq, username, password)
|
||||
http.Redirect(w, rq, "/"+rq.URL.RawQuery, http.StatusSeeOther)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func handlerLogout(w http.ResponseWriter, rq *http.Request) {
|
||||
var (
|
||||
u = user.FromRequest(rq)
|
||||
can = u != nil
|
||||
)
|
||||
w.Header().Set("Content-Type", "text/html;charset=utf-8")
|
||||
if can {
|
||||
log.Println("User", u.Name, "tries to log out")
|
||||
w.WriteHeader(http.StatusOK)
|
||||
} else {
|
||||
log.Println("Unknown user tries to log out")
|
||||
w.WriteHeader(http.StatusForbidden)
|
||||
}
|
||||
w.Write([]byte(views.BaseHTML("Logout?", views.LogoutHTML(can), u)))
|
||||
}
|
||||
|
||||
func handlerLogoutConfirm(w http.ResponseWriter, rq *http.Request) {
|
||||
user.LogoutFromRequest(w, rq)
|
||||
http.Redirect(w, rq, "/", http.StatusSeeOther)
|
||||
}
|
||||
|
||||
func handlerLoginData(w http.ResponseWriter, rq *http.Request) {
|
||||
util.PrepareRq(rq)
|
||||
var (
|
||||
username = util.CanonicalName(rq.PostFormValue("username"))
|
||||
password = rq.PostFormValue("password")
|
||||
err = user.LoginDataHTTP(w, rq, username, password)
|
||||
)
|
||||
if err != "" {
|
||||
w.Write([]byte(views.BaseHTML(err, views.LoginErrorHTML(err), user.EmptyUser())))
|
||||
} else {
|
||||
http.Redirect(w, rq, "/", http.StatusSeeOther)
|
||||
}
|
||||
}
|
||||
|
||||
func handlerLogin(w http.ResponseWriter, rq *http.Request) {
|
||||
util.PrepareRq(rq)
|
||||
w.Header().Set("Content-Type", "text/html;charset=utf-8")
|
||||
if user.AuthUsed {
|
||||
w.WriteHeader(http.StatusOK)
|
||||
} else {
|
||||
w.WriteHeader(http.StatusForbidden)
|
||||
}
|
||||
w.Write([]byte(views.BaseHTML("Login", views.LoginHTML(), user.EmptyUser())))
|
||||
}
|
||||
78
web/http_history.go
Normal file
78
web/http_history.go
Normal file
@@ -0,0 +1,78 @@
|
||||
package web
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/bouncepaw/mycorrhiza/history"
|
||||
"github.com/bouncepaw/mycorrhiza/user"
|
||||
"github.com/bouncepaw/mycorrhiza/util"
|
||||
"github.com/bouncepaw/mycorrhiza/views"
|
||||
)
|
||||
|
||||
func init() {
|
||||
http.HandleFunc("/history/", handlerHistory)
|
||||
http.HandleFunc("/recent-changes/", handlerRecentChanges)
|
||||
http.HandleFunc("/recent-changes-rss", handlerRecentChangesRSS)
|
||||
http.HandleFunc("/recent-changes-atom", handlerRecentChangesAtom)
|
||||
http.HandleFunc("/recent-changes-json", handlerRecentChangesJSON)
|
||||
}
|
||||
|
||||
// handlerHistory lists all revisions of a hypha
|
||||
func handlerHistory(w http.ResponseWriter, rq *http.Request) {
|
||||
util.PrepareRq(rq)
|
||||
hyphaName := util.HyphaNameFromRq(rq, "history")
|
||||
var list string
|
||||
|
||||
// History can be found for files that do not exist anymore.
|
||||
revs, err := history.Revisions(hyphaName)
|
||||
if err == nil {
|
||||
list = history.HistoryWithRevisions(hyphaName, revs)
|
||||
}
|
||||
log.Println("Found", len(revs), "revisions for", hyphaName)
|
||||
|
||||
util.HTTP200Page(w,
|
||||
views.BaseHTML(hyphaName, views.HistoryHTML(rq, hyphaName, list), user.FromRequest(rq)))
|
||||
}
|
||||
|
||||
// Recent changes
|
||||
func handlerRecentChanges(w http.ResponseWriter, rq *http.Request) {
|
||||
util.PrepareRq(rq)
|
||||
var (
|
||||
noPrefix = strings.TrimPrefix(rq.URL.String(), "/recent-changes/")
|
||||
n, err = strconv.Atoi(noPrefix)
|
||||
)
|
||||
if err == nil && n < 101 {
|
||||
util.HTTP200Page(w, views.BaseHTML(strconv.Itoa(n)+" recent changes", views.RecentChangesHTML(n), user.FromRequest(rq)))
|
||||
} else {
|
||||
http.Redirect(w, rq, "/recent-changes/20", http.StatusSeeOther)
|
||||
}
|
||||
}
|
||||
|
||||
func genericHandlerOfFeeds(w http.ResponseWriter, rq *http.Request, f func() (string, error), name string) {
|
||||
util.PrepareRq(rq)
|
||||
if content, err := f(); err != nil {
|
||||
w.Header().Set("Content-Type", "text/plain;charset=utf-8")
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
fmt.Fprint(w, "An error while generating "+name+": "+err.Error())
|
||||
} else {
|
||||
w.Header().Set("Content-Type", "application/rss+xml")
|
||||
w.WriteHeader(http.StatusOK)
|
||||
fmt.Fprint(w, content)
|
||||
}
|
||||
}
|
||||
|
||||
func handlerRecentChangesRSS(w http.ResponseWriter, rq *http.Request) {
|
||||
genericHandlerOfFeeds(w, rq, history.RecentChangesRSS, "RSS")
|
||||
}
|
||||
|
||||
func handlerRecentChangesAtom(w http.ResponseWriter, rq *http.Request) {
|
||||
genericHandlerOfFeeds(w, rq, history.RecentChangesAtom, "Atom")
|
||||
}
|
||||
|
||||
func handlerRecentChangesJSON(w http.ResponseWriter, rq *http.Request) {
|
||||
genericHandlerOfFeeds(w, rq, history.RecentChangesJSON, "JSON feed")
|
||||
}
|
||||
247
web/http_mutators.go
Normal file
247
web/http_mutators.go
Normal file
@@ -0,0 +1,247 @@
|
||||
package web
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"net/http"
|
||||
|
||||
"github.com/bouncepaw/mycorrhiza/history"
|
||||
"github.com/bouncepaw/mycorrhiza/hyphae"
|
||||
"github.com/bouncepaw/mycorrhiza/markup"
|
||||
"github.com/bouncepaw/mycorrhiza/shroom"
|
||||
"github.com/bouncepaw/mycorrhiza/user"
|
||||
"github.com/bouncepaw/mycorrhiza/util"
|
||||
"github.com/bouncepaw/mycorrhiza/views"
|
||||
)
|
||||
|
||||
func init() {
|
||||
// Those that do not actually mutate anything:
|
||||
http.HandleFunc("/edit/", handlerEdit)
|
||||
http.HandleFunc("/delete-ask/", handlerDeleteAsk)
|
||||
http.HandleFunc("/rename-ask/", handlerRenameAsk)
|
||||
http.HandleFunc("/unattach-ask/", handlerUnattachAsk)
|
||||
// And those that do mutate something:
|
||||
http.HandleFunc("/upload-binary/", handlerUploadBinary)
|
||||
http.HandleFunc("/upload-text/", handlerUploadText)
|
||||
http.HandleFunc("/delete-confirm/", handlerDeleteConfirm)
|
||||
http.HandleFunc("/rename-confirm/", handlerRenameConfirm)
|
||||
http.HandleFunc("/unattach-confirm/", handlerUnattachConfirm)
|
||||
}
|
||||
|
||||
func factoryHandlerAsker(
|
||||
actionPath string,
|
||||
asker func(*user.User, *hyphae.Hypha) (error, string),
|
||||
succTitleTemplate string,
|
||||
succPageTemplate func(*http.Request, string, bool) string,
|
||||
) func(http.ResponseWriter, *http.Request) {
|
||||
return func(w http.ResponseWriter, rq *http.Request) {
|
||||
util.PrepareRq(rq)
|
||||
var (
|
||||
hyphaName = util.HyphaNameFromRq(rq, actionPath)
|
||||
h = hyphae.ByName(hyphaName)
|
||||
u = user.FromRequest(rq)
|
||||
)
|
||||
if err, errtitle := asker(u, h); err != nil {
|
||||
HttpErr(
|
||||
w,
|
||||
http.StatusInternalServerError,
|
||||
hyphaName,
|
||||
errtitle,
|
||||
err.Error())
|
||||
return
|
||||
}
|
||||
util.HTTP200Page(
|
||||
w,
|
||||
views.BaseHTML(
|
||||
fmt.Sprintf(succTitleTemplate, hyphaName),
|
||||
succPageTemplate(rq, hyphaName, h.Exists),
|
||||
u))
|
||||
}
|
||||
}
|
||||
|
||||
var handlerUnattachAsk = factoryHandlerAsker(
|
||||
"unattach-ask",
|
||||
shroom.CanUnattach,
|
||||
"Unattach %s?",
|
||||
views.UnattachAskHTML,
|
||||
)
|
||||
|
||||
var handlerDeleteAsk = factoryHandlerAsker(
|
||||
"delete-ask",
|
||||
shroom.CanDelete,
|
||||
"Delete %s?",
|
||||
views.DeleteAskHTML,
|
||||
)
|
||||
|
||||
var handlerRenameAsk = factoryHandlerAsker(
|
||||
"rename-ask",
|
||||
shroom.CanRename,
|
||||
"Rename %s?",
|
||||
views.RenameAskHTML,
|
||||
)
|
||||
|
||||
func factoryHandlerConfirmer(
|
||||
actionPath string,
|
||||
confirmer func(*hyphae.Hypha, *user.User, *http.Request) (*history.HistoryOp, string),
|
||||
) func(http.ResponseWriter, *http.Request) {
|
||||
return func(w http.ResponseWriter, rq *http.Request) {
|
||||
util.PrepareRq(rq)
|
||||
var (
|
||||
hyphaName = util.HyphaNameFromRq(rq, actionPath)
|
||||
h = hyphae.ByName(hyphaName)
|
||||
u = user.FromRequest(rq)
|
||||
)
|
||||
if hop, errtitle := confirmer(h, u, rq); hop.HasErrors() {
|
||||
HttpErr(w, http.StatusInternalServerError, hyphaName,
|
||||
errtitle,
|
||||
hop.FirstErrorText())
|
||||
return
|
||||
}
|
||||
http.Redirect(w, rq, "/hypha/"+hyphaName, http.StatusSeeOther)
|
||||
}
|
||||
}
|
||||
|
||||
var handlerUnattachConfirm = factoryHandlerConfirmer(
|
||||
"unattach-confirm",
|
||||
func(h *hyphae.Hypha, u *user.User, _ *http.Request) (*history.HistoryOp, string) {
|
||||
return shroom.UnattachHypha(u, h)
|
||||
},
|
||||
)
|
||||
|
||||
var handlerDeleteConfirm = factoryHandlerConfirmer(
|
||||
"delete-confirm",
|
||||
func(h *hyphae.Hypha, u *user.User, _ *http.Request) (*history.HistoryOp, string) {
|
||||
return shroom.DeleteHypha(u, h)
|
||||
},
|
||||
)
|
||||
|
||||
var handlerRenameConfirm = factoryHandlerConfirmer(
|
||||
"rename-confirm",
|
||||
func(oldHypha *hyphae.Hypha, u *user.User, rq *http.Request) (*history.HistoryOp, string) {
|
||||
var (
|
||||
newName = util.CanonicalName(rq.PostFormValue("new-name"))
|
||||
recursive = rq.PostFormValue("recursive") == "true"
|
||||
newHypha = hyphae.ByName(newName)
|
||||
)
|
||||
return shroom.RenameHypha(oldHypha, newHypha, recursive, u)
|
||||
},
|
||||
)
|
||||
|
||||
// handlerEdit shows the edit form. It doesn't edit anything actually.
|
||||
func handlerEdit(w http.ResponseWriter, rq *http.Request) {
|
||||
util.PrepareRq(rq)
|
||||
var (
|
||||
hyphaName = util.HyphaNameFromRq(rq, "edit")
|
||||
h = hyphae.ByName(hyphaName)
|
||||
warning string
|
||||
textAreaFill string
|
||||
err error
|
||||
u = user.FromRequest(rq)
|
||||
)
|
||||
if err, errtitle := shroom.CanEdit(u, h); err != nil {
|
||||
HttpErr(w, http.StatusInternalServerError, hyphaName,
|
||||
errtitle,
|
||||
err.Error())
|
||||
return
|
||||
}
|
||||
if h.Exists {
|
||||
textAreaFill, err = shroom.FetchTextPart(h)
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
HttpErr(w, http.StatusInternalServerError, hyphaName,
|
||||
"Error",
|
||||
"Could not fetch text data")
|
||||
return
|
||||
}
|
||||
} else {
|
||||
warning = `<p class="warning warning_new-hypha">You are creating a new hypha.</p>`
|
||||
}
|
||||
util.HTTP200Page(
|
||||
w,
|
||||
views.BaseHTML(
|
||||
"Edit "+hyphaName,
|
||||
views.EditHTML(rq, hyphaName, textAreaFill, warning),
|
||||
u))
|
||||
}
|
||||
|
||||
// handlerUploadText uploads a new text part for the hypha.
|
||||
func handlerUploadText(w http.ResponseWriter, rq *http.Request) {
|
||||
util.PrepareRq(rq)
|
||||
var (
|
||||
hyphaName = util.HyphaNameFromRq(rq, "upload-text")
|
||||
h = hyphae.ByName(hyphaName)
|
||||
textData = rq.PostFormValue("text")
|
||||
action = rq.PostFormValue("action")
|
||||
u = user.FromRequest(rq)
|
||||
hop *history.HistoryOp
|
||||
errtitle string
|
||||
)
|
||||
|
||||
if action != "Preview" {
|
||||
hop, errtitle = shroom.UploadText(h, []byte(textData), u)
|
||||
if hop.HasErrors() {
|
||||
HttpErr(w, http.StatusForbidden, hyphaName,
|
||||
errtitle,
|
||||
hop.FirstErrorText())
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
if action == "Preview" {
|
||||
util.HTTP200Page(
|
||||
w,
|
||||
views.BaseHTML(
|
||||
"Preview "+hyphaName,
|
||||
views.PreviewHTML(
|
||||
rq,
|
||||
hyphaName,
|
||||
textData,
|
||||
"",
|
||||
markup.Doc(hyphaName, textData).AsHTML()),
|
||||
u))
|
||||
} else {
|
||||
http.Redirect(w, rq, "/hypha/"+hyphaName, http.StatusSeeOther)
|
||||
}
|
||||
}
|
||||
|
||||
// handlerUploadBinary uploads a new binary part for the hypha.
|
||||
func handlerUploadBinary(w http.ResponseWriter, rq *http.Request) {
|
||||
util.PrepareRq(rq)
|
||||
rq.ParseMultipartForm(10 << 20) // Set upload limit
|
||||
var (
|
||||
hyphaName = util.HyphaNameFromRq(rq, "upload-binary")
|
||||
h = hyphae.ByName(hyphaName)
|
||||
u = user.FromRequest(rq)
|
||||
file, handler, err = rq.FormFile("binary")
|
||||
)
|
||||
if err != nil {
|
||||
HttpErr(w, http.StatusInternalServerError, hyphaName,
|
||||
"Error",
|
||||
err.Error())
|
||||
}
|
||||
if err, errtitle := shroom.CanAttach(u, h); err != nil {
|
||||
HttpErr(w, http.StatusInternalServerError, hyphaName,
|
||||
errtitle,
|
||||
err.Error())
|
||||
}
|
||||
|
||||
// If file is not passed:
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// If file is passed:
|
||||
if file != nil {
|
||||
defer file.Close()
|
||||
}
|
||||
var (
|
||||
mime = handler.Header.Get("Content-Type")
|
||||
hop, errtitle = shroom.UploadBinary(h, mime, file, u)
|
||||
)
|
||||
|
||||
if hop.HasErrors() {
|
||||
HttpErr(w, http.StatusInternalServerError, hyphaName, errtitle, hop.FirstErrorText())
|
||||
return
|
||||
}
|
||||
http.Redirect(w, rq, "/hypha/"+hyphaName, http.StatusSeeOther)
|
||||
}
|
||||
148
web/http_readers.go
Normal file
148
web/http_readers.go
Normal file
@@ -0,0 +1,148 @@
|
||||
package web
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"net/http"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/bouncepaw/mycorrhiza/history"
|
||||
"github.com/bouncepaw/mycorrhiza/hyphae"
|
||||
"github.com/bouncepaw/mycorrhiza/markup"
|
||||
"github.com/bouncepaw/mycorrhiza/mimetype"
|
||||
"github.com/bouncepaw/mycorrhiza/user"
|
||||
"github.com/bouncepaw/mycorrhiza/util"
|
||||
"github.com/bouncepaw/mycorrhiza/views"
|
||||
)
|
||||
|
||||
func init() {
|
||||
http.HandleFunc("/page/", handlerHypha)
|
||||
http.HandleFunc("/hypha/", handlerHypha)
|
||||
http.HandleFunc("/text/", handlerText)
|
||||
http.HandleFunc("/binary/", handlerBinary)
|
||||
http.HandleFunc("/rev/", handlerRevision)
|
||||
http.HandleFunc("/primitive-diff/", handlerPrimitiveDiff)
|
||||
http.HandleFunc("/attachment/", handlerAttachment)
|
||||
}
|
||||
|
||||
func handlerAttachment(w http.ResponseWriter, rq *http.Request) {
|
||||
util.PrepareRq(rq)
|
||||
var (
|
||||
hyphaName = util.HyphaNameFromRq(rq, "attachment")
|
||||
h = hyphae.ByName(hyphaName)
|
||||
u = user.FromRequest(rq)
|
||||
)
|
||||
util.HTTP200Page(w,
|
||||
views.BaseHTML(
|
||||
fmt.Sprintf("Attachment of %s", util.BeautifulName(hyphaName)),
|
||||
views.AttachmentMenuHTML(rq, h, u),
|
||||
u))
|
||||
}
|
||||
|
||||
func handlerPrimitiveDiff(w http.ResponseWriter, rq *http.Request) {
|
||||
util.PrepareRq(rq)
|
||||
var (
|
||||
shorterUrl = strings.TrimPrefix(rq.URL.Path, "/primitive-diff/")
|
||||
firstSlashIndex = strings.IndexRune(shorterUrl, '/')
|
||||
revHash = shorterUrl[:firstSlashIndex]
|
||||
hyphaName = util.CanonicalName(shorterUrl[firstSlashIndex+1:])
|
||||
h = hyphae.ByName(hyphaName)
|
||||
u = user.FromRequest(rq)
|
||||
)
|
||||
util.HTTP200Page(w,
|
||||
views.BaseHTML(
|
||||
fmt.Sprintf("Diff of %s at %s", hyphaName, revHash),
|
||||
views.PrimitiveDiffHTML(rq, h, u, revHash),
|
||||
u))
|
||||
}
|
||||
|
||||
// handlerRevision displays a specific revision of text part a page
|
||||
func handlerRevision(w http.ResponseWriter, rq *http.Request) {
|
||||
util.PrepareRq(rq)
|
||||
var (
|
||||
shorterUrl = strings.TrimPrefix(rq.URL.Path, "/rev/")
|
||||
firstSlashIndex = strings.IndexRune(shorterUrl, '/')
|
||||
revHash = shorterUrl[:firstSlashIndex]
|
||||
hyphaName = util.CanonicalName(shorterUrl[firstSlashIndex+1:])
|
||||
h = hyphae.ByName(hyphaName)
|
||||
contents = fmt.Sprintf(`<p>This hypha had no text at this revision.</p>`)
|
||||
textContents, err = history.FileAtRevision(h.TextPath, revHash)
|
||||
u = user.FromRequest(rq)
|
||||
)
|
||||
if err == nil {
|
||||
contents = markup.Doc(hyphaName, textContents).AsHTML()
|
||||
}
|
||||
page := views.RevisionHTML(
|
||||
rq,
|
||||
h,
|
||||
contents,
|
||||
revHash,
|
||||
)
|
||||
w.Header().Set("Content-Type", "text/html;charset=utf-8")
|
||||
w.WriteHeader(http.StatusOK)
|
||||
w.Write([]byte(views.BaseHTML(util.BeautifulName(hyphaName), page, u)))
|
||||
}
|
||||
|
||||
// handlerText serves raw source text of the hypha.
|
||||
func handlerText(w http.ResponseWriter, rq *http.Request) {
|
||||
util.PrepareRq(rq)
|
||||
hyphaName := util.HyphaNameFromRq(rq, "text")
|
||||
if h := hyphae.ByName(hyphaName); h.Exists {
|
||||
log.Println("Serving", h.TextPath)
|
||||
w.Header().Set("Content-Type", "text/plain; charset=utf-8")
|
||||
http.ServeFile(w, rq, h.TextPath)
|
||||
}
|
||||
}
|
||||
|
||||
// handlerBinary serves binary part of the hypha.
|
||||
func handlerBinary(w http.ResponseWriter, rq *http.Request) {
|
||||
util.PrepareRq(rq)
|
||||
hyphaName := util.HyphaNameFromRq(rq, "binary")
|
||||
if h := hyphae.ByName(hyphaName); h.Exists {
|
||||
log.Println("Serving", h.BinaryPath)
|
||||
w.Header().Set("Content-Type", mimetype.FromExtension(filepath.Ext(h.BinaryPath)))
|
||||
http.ServeFile(w, rq, h.BinaryPath)
|
||||
}
|
||||
}
|
||||
|
||||
// handlerHypha is the main hypha action that displays the hypha and the binary upload form along with some navigation.
|
||||
func handlerHypha(w http.ResponseWriter, rq *http.Request) {
|
||||
util.PrepareRq(rq)
|
||||
var (
|
||||
hyphaName = util.HyphaNameFromRq(rq, "page", "hypha")
|
||||
h = hyphae.ByName(hyphaName)
|
||||
contents string
|
||||
openGraph string
|
||||
u = user.FromRequest(rq)
|
||||
)
|
||||
if h.Exists {
|
||||
fileContentsT, errT := ioutil.ReadFile(h.TextPath)
|
||||
_, errB := os.Stat(h.BinaryPath)
|
||||
if errT == nil {
|
||||
md := markup.Doc(hyphaName, string(fileContentsT))
|
||||
contents = md.AsHTML()
|
||||
openGraph = md.OpenGraphHTML()
|
||||
}
|
||||
if !os.IsNotExist(errB) {
|
||||
contents = views.AttachmentHTML(h) + contents
|
||||
}
|
||||
}
|
||||
if contents == "" {
|
||||
util.HTTP404Page(w,
|
||||
views.BaseHTML(
|
||||
util.BeautifulName(hyphaName),
|
||||
views.HyphaHTML(rq, h, contents),
|
||||
u,
|
||||
openGraph))
|
||||
} else {
|
||||
util.HTTP200Page(w,
|
||||
views.BaseHTML(
|
||||
util.BeautifulName(hyphaName),
|
||||
views.HyphaHTML(rq, h, contents),
|
||||
u,
|
||||
openGraph))
|
||||
}
|
||||
}
|
||||
92
web/http_stuff.go
Normal file
92
web/http_stuff.go
Normal file
@@ -0,0 +1,92 @@
|
||||
// http_stuff.go is used for meta stuff about the wiki or all hyphae at once.
|
||||
package web
|
||||
|
||||
import (
|
||||
"github.com/bouncepaw/mycorrhiza/cfg"
|
||||
"io"
|
||||
"log"
|
||||
"math/rand"
|
||||
"net/http"
|
||||
|
||||
"github.com/bouncepaw/mycorrhiza/hyphae"
|
||||
"github.com/bouncepaw/mycorrhiza/shroom"
|
||||
"github.com/bouncepaw/mycorrhiza/user"
|
||||
"github.com/bouncepaw/mycorrhiza/util"
|
||||
"github.com/bouncepaw/mycorrhiza/views"
|
||||
)
|
||||
|
||||
func init() {
|
||||
http.HandleFunc("/list/", handlerList)
|
||||
http.HandleFunc("/reindex/", handlerReindex)
|
||||
http.HandleFunc("/update-header-links/", handlerUpdateHeaderLinks)
|
||||
http.HandleFunc("/random/", handlerRandom)
|
||||
http.HandleFunc("/about/", handlerAbout)
|
||||
}
|
||||
|
||||
// handlerList shows a list of all hyphae in the wiki in random order.
|
||||
func handlerList(w http.ResponseWriter, rq *http.Request) {
|
||||
util.PrepareRq(rq)
|
||||
util.HTTP200Page(w, views.BaseHTML("List of pages", views.HyphaListHTML(), user.FromRequest(rq)))
|
||||
}
|
||||
|
||||
// handlerReindex reindexes all hyphae by checking the wiki storage directory anew.
|
||||
func handlerReindex(w http.ResponseWriter, rq *http.Request) {
|
||||
util.PrepareRq(rq)
|
||||
if ok := user.CanProceed(rq, "reindex"); !ok {
|
||||
HttpErr(w, http.StatusForbidden, cfg.HomeHypha, "Not enough rights", "You must be an admin to reindex hyphae.")
|
||||
log.Println("Rejected", rq.URL)
|
||||
return
|
||||
}
|
||||
hyphae.ResetCount()
|
||||
log.Println("Wiki storage directory is", cfg.WikiDir)
|
||||
log.Println("Start indexing hyphae...")
|
||||
hyphae.Index(cfg.WikiDir)
|
||||
log.Println("Indexed", hyphae.Count(), "hyphae")
|
||||
http.Redirect(w, rq, "/", http.StatusSeeOther)
|
||||
}
|
||||
|
||||
// handlerUpdateHeaderLinks updates header links by reading the configured hypha, if there is any, or resorting to default values.
|
||||
//
|
||||
// See https://mycorrhiza.lesarbr.es/hypha/configuration/header
|
||||
func handlerUpdateHeaderLinks(w http.ResponseWriter, rq *http.Request) {
|
||||
util.PrepareRq(rq)
|
||||
if ok := user.CanProceed(rq, "update-header-links"); !ok {
|
||||
HttpErr(w, http.StatusForbidden, cfg.HomeHypha, "Not enough rights", "You must be a moderator to update header links.")
|
||||
log.Println("Rejected", rq.URL)
|
||||
return
|
||||
}
|
||||
shroom.SetHeaderLinks()
|
||||
http.Redirect(w, rq, "/", http.StatusSeeOther)
|
||||
}
|
||||
|
||||
// handlerRandom redirects to a random hypha.
|
||||
func handlerRandom(w http.ResponseWriter, rq *http.Request) {
|
||||
util.PrepareRq(rq)
|
||||
var (
|
||||
randomHyphaName string
|
||||
amountOfHyphae = hyphae.Count()
|
||||
)
|
||||
if amountOfHyphae == 0 {
|
||||
HttpErr(w, http.StatusNotFound, cfg.HomeHypha, "There are no hyphae",
|
||||
"It is impossible to display a random hypha because the wiki does not contain any hyphae")
|
||||
return
|
||||
}
|
||||
i := rand.Intn(amountOfHyphae)
|
||||
for h := range hyphae.YieldExistingHyphae() {
|
||||
if i == 0 {
|
||||
randomHyphaName = h.Name
|
||||
}
|
||||
i--
|
||||
}
|
||||
http.Redirect(w, rq, "/hypha/"+randomHyphaName, http.StatusSeeOther)
|
||||
}
|
||||
|
||||
// handlerAbout shows a summary of wiki's software.
|
||||
func handlerAbout(w http.ResponseWriter, rq *http.Request) {
|
||||
w.Header().Set("Content-Type", "text/html;charset=utf-8")
|
||||
w.WriteHeader(http.StatusOK)
|
||||
_, err := io.WriteString(w, views.BaseHTML("About "+cfg.WikiName, views.AboutHTML(), user.FromRequest(rq)))
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
}
|
||||
}
|
||||
127
web/web.go
Normal file
127
web/web.go
Normal file
@@ -0,0 +1,127 @@
|
||||
package web
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/bouncepaw/mycorrhiza/assets"
|
||||
"github.com/bouncepaw/mycorrhiza/cfg"
|
||||
"github.com/bouncepaw/mycorrhiza/util"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/bouncepaw/mycorrhiza/user"
|
||||
"github.com/bouncepaw/mycorrhiza/views"
|
||||
)
|
||||
|
||||
// HttpErr is used by many handlers to signal errors in a compact way.
|
||||
func HttpErr(w http.ResponseWriter, status int, name, title, errMsg string) {
|
||||
log.Println(errMsg, "for", name)
|
||||
w.Header().Set("Content-Type", "text/html;charset=utf-8")
|
||||
w.WriteHeader(status)
|
||||
fmt.Fprint(
|
||||
w,
|
||||
views.BaseHTML(
|
||||
title,
|
||||
fmt.Sprintf(
|
||||
`<main class="main-width"><p>%s. <a href="/page/%s">Go back to the hypha.<a></p></main>`,
|
||||
errMsg,
|
||||
name,
|
||||
),
|
||||
user.EmptyUser(),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
func handlerStyle(w http.ResponseWriter, rq *http.Request) {
|
||||
util.PrepareRq(rq)
|
||||
if _, err := os.Stat(cfg.WikiDir + "/static/common.css"); err == nil {
|
||||
http.ServeFile(w, rq, cfg.WikiDir+"/static/common.css")
|
||||
} else {
|
||||
w.Header().Set("Content-Type", "text/css;charset=utf-8")
|
||||
w.Write([]byte(assets.DefaultCSS()))
|
||||
}
|
||||
if bytes, err := ioutil.ReadFile(cfg.WikiDir + "/static/custom.css"); err == nil {
|
||||
w.Write(bytes)
|
||||
}
|
||||
}
|
||||
|
||||
func handlerToolbar(w http.ResponseWriter, rq *http.Request) {
|
||||
util.PrepareRq(rq)
|
||||
w.Header().Set("Content-Type", "text/javascript;charset=utf-8")
|
||||
w.Write([]byte(assets.ToolbarJS()))
|
||||
}
|
||||
|
||||
// handlerIcon serves the requested icon. All icons are distributed as part of the Mycorrhiza binary.
|
||||
//
|
||||
// See assets/assets/icon/ for icons themselves, see assets/assets.qtpl for their sources.
|
||||
func handlerIcon(w http.ResponseWriter, rq *http.Request) {
|
||||
iconName := strings.TrimPrefix(rq.URL.Path, "/assets/icon/")
|
||||
if iconName == "https" {
|
||||
iconName = "http"
|
||||
}
|
||||
w.Header().Set("Content-Type", "image/svg+xml")
|
||||
icon := func() string {
|
||||
switch iconName {
|
||||
case "gemini":
|
||||
return assets.IconGemini()
|
||||
case "mailto":
|
||||
return assets.IconMailto()
|
||||
case "gopher":
|
||||
return assets.IconGopher()
|
||||
case "feed":
|
||||
return assets.IconFeed()
|
||||
default:
|
||||
return assets.IconHTTP()
|
||||
}
|
||||
}()
|
||||
_, err := io.WriteString(w, icon)
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func handlerUserList(w http.ResponseWriter, rq *http.Request) {
|
||||
w.Header().Set("Content-Type", "text/html;charset=utf-8")
|
||||
w.WriteHeader(http.StatusOK)
|
||||
w.Write([]byte(views.BaseHTML("User list", views.UserListHTML(), user.FromRequest(rq))))
|
||||
}
|
||||
|
||||
func handlerRobotsTxt(w http.ResponseWriter, rq *http.Request) {
|
||||
w.Header().Set("Content-Type", "text/plain")
|
||||
w.WriteHeader(http.StatusOK)
|
||||
w.Write([]byte(
|
||||
`User-agent: *
|
||||
Allow: /page/
|
||||
Allow: /recent-changes
|
||||
Disallow: /
|
||||
Crawl-delay: 5`))
|
||||
}
|
||||
|
||||
func Init() {
|
||||
// See http_admin.go for /admin, /admin/*
|
||||
InitAdmin()
|
||||
// See http_readers.go for /page/, /hypha/, /text/, /binary/, /attachment/
|
||||
// See http_mutators.go for /upload-binary/, /upload-text/, /edit/, /delete-ask/, /delete-confirm/, /rename-ask/, /rename-confirm/, /unattach-ask/, /unattach-confirm/
|
||||
// See http_auth.go for /login, /login-data, /logout, /logout-confirm
|
||||
http.HandleFunc("/user-list/", handlerUserList)
|
||||
// See http_history.go for /history/, /recent-changes
|
||||
// See http_stuff.go for list, reindex, update-header-links, random, about
|
||||
http.Handle("/static/", http.StripPrefix("/static/", http.FileServer(http.Dir(cfg.WikiDir+"/static"))))
|
||||
http.HandleFunc("/favicon.ico", func(w http.ResponseWriter, rq *http.Request) {
|
||||
http.ServeFile(w, rq, cfg.WikiDir+"/static/favicon.ico")
|
||||
})
|
||||
http.HandleFunc("/static/common.css", handlerStyle)
|
||||
http.HandleFunc("/static/toolbar.js", handlerToolbar)
|
||||
http.HandleFunc("/assets/icon/", handlerIcon)
|
||||
http.HandleFunc("/robots.txt", handlerRobotsTxt)
|
||||
http.HandleFunc("/", func(w http.ResponseWriter, rq *http.Request) {
|
||||
addr, _ := url.Parse("/hypha/" + cfg.HomeHypha) // Let's pray it never fails
|
||||
rq.URL = addr
|
||||
handlerHypha(w, rq)
|
||||
})
|
||||
}
|
||||
Reference in New Issue
Block a user