1
0
mirror of https://github.com/osmarks/mycorrhiza.git synced 2025-03-10 13:38:20 +00:00
mycorrhiza/web/readers.go

299 lines
10 KiB
Go
Raw Normal View History

package web
import (
"fmt"
"html/template"
"io"
"log/slog"
"net/http"
"os"
"path"
"path/filepath"
"strings"
"time"
"github.com/bouncepaw/mycorrhiza/history"
"github.com/bouncepaw/mycorrhiza/hypview"
"github.com/bouncepaw/mycorrhiza/internal/backlinks"
"github.com/bouncepaw/mycorrhiza/internal/categories"
"github.com/bouncepaw/mycorrhiza/internal/cfg"
"github.com/bouncepaw/mycorrhiza/internal/files"
"github.com/bouncepaw/mycorrhiza/internal/hyphae"
"github.com/bouncepaw/mycorrhiza/internal/mimetype"
"github.com/bouncepaw/mycorrhiza/internal/tree"
"github.com/bouncepaw/mycorrhiza/internal/user"
"github.com/bouncepaw/mycorrhiza/l18n"
2022-06-10 18:45:27 +03:00
"github.com/bouncepaw/mycorrhiza/mycoopts"
"github.com/bouncepaw/mycorrhiza/util"
"github.com/bouncepaw/mycorrhiza/web/viewutil"
"git.sr.ht/~bouncepaw/mycomarkup/v5"
"git.sr.ht/~bouncepaw/mycomarkup/v5/mycocontext"
"git.sr.ht/~bouncepaw/mycomarkup/v5/tools"
"github.com/gorilla/mux"
)
func initReaders(r *mux.Router) {
r.PathPrefix("/page/").HandlerFunc(handlerHypha)
r.PathPrefix("/hypha/").HandlerFunc(handlerHypha)
r.PathPrefix("/text/").HandlerFunc(handlerText)
r.PathPrefix("/binary/").HandlerFunc(handlerBinary)
r.PathPrefix("/rev/").HandlerFunc(handlerRevision)
2021-09-23 12:36:54 +03:00
r.PathPrefix("/rev-text/").HandlerFunc(handlerRevisionText)
2022-02-26 10:33:09 +03:00
r.PathPrefix("/media/").HandlerFunc(handlerMedia)
r.Path("/today").HandlerFunc(handlerToday)
r.Path("/edit-today").HandlerFunc(handlerEditToday)
// Backlinks
r.PathPrefix("/backlinks/").HandlerFunc(handlerBacklinks)
r.PathPrefix("/orphans").HandlerFunc(handlerOrphans)
}
func handlerEditToday(w http.ResponseWriter, rq *http.Request) {
today := time.Now().Format(time.DateOnly)
http.Redirect(w, rq, "/edit/"+today, http.StatusSeeOther)
}
func handlerToday(w http.ResponseWriter, rq *http.Request) {
today := time.Now().Format(time.DateOnly)
http.Redirect(w, rq, "/hypha/"+today, http.StatusSeeOther)
2021-02-24 22:34:42 +05:00
}
2022-02-26 10:33:09 +03:00
func handlerMedia(w http.ResponseWriter, rq *http.Request) {
util.PrepareRq(rq)
2021-02-24 22:34:42 +05:00
var (
2022-02-26 10:33:09 +03:00
hyphaName = util.HyphaNameFromRq(rq, "media")
2021-02-24 22:34:42 +05:00
h = hyphae.ByName(hyphaName)
u = user.FromRequest(rq)
isMedia = false
mime string
fileSize int64
2021-02-24 22:34:42 +05:00
)
switch h := h.(type) {
case *hyphae.MediaHypha:
isMedia = true
mime = mimetype.FromExtension(path.Ext(h.MediaFilePath()))
fileinfo, err := os.Stat(h.MediaFilePath())
if err != nil {
slog.Error("failed to stat media file", "err", err)
// no return
}
fileSize = fileinfo.Size()
}
_ = pageMedia.RenderTo(viewutil.MetaFrom(w, rq), map[string]any{
"HyphaName": h.CanonicalName(),
"U": u,
"IsMediaHypha": isMedia,
"MimeType": mime,
"FileSize": fileSize,
})
}
2021-09-23 12:36:54 +03:00
// handlerRevisionText sends Mycomarkup text of the hypha at the given revision. See also: handlerRevision, handlerText.
//
// /rev-text/<revHash>/<hyphaName>
func handlerRevisionText(w http.ResponseWriter, rq *http.Request) {
util.PrepareRq(rq)
shorterURL := strings.TrimPrefix(rq.URL.Path, "/rev-text/")
revHash, slug, found := strings.Cut(shorterURL, "/")
if !found || !util.IsRevHash(revHash) || len(slug) < 1 {
http.Error(w, "403 bad request", http.StatusBadRequest)
return
}
2021-09-23 12:36:54 +03:00
var (
hyphaName = util.CanonicalName(slug)
h = hyphae.ByName(hyphaName)
2021-09-23 12:36:54 +03:00
)
w.Header().Set("Content-Type", "text/plain; charset=utf-8")
2022-02-19 11:26:38 +03:00
switch h := h.(type) {
case *hyphae.EmptyHypha:
var mycoFilePath = filepath.Join(files.HyphaeDir(), h.CanonicalName()+".myco")
var textContents, err = history.FileAtRevision(mycoFilePath, revHash)
if err != nil {
w.WriteHeader(http.StatusNotFound)
slog.Error("Failed to serve text part",
"err", err, "hyphaName", hyphaName, "revHash", revHash)
_, _ = io.WriteString(w, "Error: "+err.Error())
return
}
slog.Info("Serving text part",
"hyphaName", hyphaName, "revHash", revHash, "mycoFilePath", mycoFilePath)
w.WriteHeader(http.StatusOK)
_, _ = io.WriteString(w, textContents)
2022-02-19 11:26:38 +03:00
case hyphae.ExistingHypha:
if !h.HasTextFile() {
slog.Info("Media hypha has no text part; cannot serve it",
"hyphaName", h.CanonicalName())
2022-02-19 11:26:38 +03:00
w.WriteHeader(http.StatusNotFound)
}
var textContents, err = history.FileAtRevision(h.TextFilePath(), revHash)
if err != nil {
w.WriteHeader(http.StatusNotFound)
slog.Error("Failed to serve text part",
"err", err, "hyphaName", h.CanonicalName(), "revHash", revHash)
2022-02-19 11:26:38 +03:00
_, _ = io.WriteString(w, "Error: "+err.Error())
return
}
slog.Info("Serving text part", "hyphaName", h.CanonicalName(), "revHash", revHash)
2022-02-19 11:26:38 +03:00
w.WriteHeader(http.StatusOK)
_, _ = io.WriteString(w, textContents)
2021-09-23 12:36:54 +03:00
}
}
// handlerRevision displays a specific revision of the text part the hypha
func handlerRevision(w http.ResponseWriter, rq *http.Request) {
util.PrepareRq(rq)
lc := l18n.FromRequest(rq)
shorterURL := strings.TrimPrefix(rq.URL.Path, "/rev/")
revHash, slug, found := strings.Cut(shorterURL, "/")
if !found || !util.IsRevHash(revHash) || len(slug) < 1 {
http.Error(w, "403 bad request", http.StatusBadRequest)
return
}
var (
hyphaName = util.CanonicalName(slug)
h = hyphae.ByName(hyphaName)
contents = template.HTML(fmt.Sprintf(`<p>%s</p>`, lc.Get("ui.revision_no_text")))
2022-05-31 13:29:13 +03:00
textContents string
err error
2022-05-31 13:33:25 +03:00
mycoFilePath string
2022-05-31 13:29:13 +03:00
)
2022-02-19 11:26:38 +03:00
switch h := h.(type) {
2022-02-19 11:29:14 +03:00
case hyphae.ExistingHypha:
2022-05-31 13:33:25 +03:00
mycoFilePath = h.TextFilePath()
2022-05-31 13:29:13 +03:00
case *hyphae.EmptyHypha:
2022-05-31 13:33:25 +03:00
mycoFilePath = filepath.Join(files.HyphaeDir(), h.CanonicalName()+".myco")
}
2022-05-31 13:33:25 +03:00
textContents, err = history.FileAtRevision(mycoFilePath, revHash)
2022-05-31 13:29:13 +03:00
if err == nil {
2022-06-10 18:45:27 +03:00
ctx, _ := mycocontext.ContextFromStringInput(textContents, mycoopts.MarkupOptions(hyphaName))
contents = template.HTML(mycomarkup.BlocksToHTML(ctx, mycomarkup.BlockTree(ctx)))
2022-05-31 13:29:13 +03:00
}
meta := viewutil.MetaFrom(w, rq)
_ = pageRevision.RenderTo(meta, map[string]any{
"ViewScripts": cfg.ViewScripts,
"Contents": contents,
"RevHash": revHash,
"NaviTitle": hypview.NaviTitle(meta, h.CanonicalName()),
"HyphaName": h.CanonicalName(),
})
2020-08-19 23:54:23 +05:00
}
// handlerText serves raw source text of the hypha.
func handlerText(w http.ResponseWriter, rq *http.Request) {
util.PrepareRq(rq)
hyphaName := util.HyphaNameFromRq(rq, "text")
switch h := hyphae.ByName(hyphaName).(type) {
2022-08-06 12:44:19 +05:00
case hyphae.ExistingHypha:
slog.Info("Serving text part", "path", h.TextFilePath())
2022-02-19 11:26:38 +03:00
w.Header().Set("Content-Type", "text/plain; charset=utf-8")
http.ServeFile(w, rq, h.TextFilePath())
}
}
// handlerBinary serves attachment of the hypha.
func handlerBinary(w http.ResponseWriter, rq *http.Request) {
util.PrepareRq(rq)
hyphaName := util.HyphaNameFromRq(rq, "binary")
switch h := hyphae.ByName(hyphaName).(type) {
case *hyphae.EmptyHypha, *hyphae.TextualHypha:
2022-02-19 11:26:38 +03:00
w.WriteHeader(http.StatusNotFound)
slog.Info("Textual hypha has no media file; cannot serve it",
"hyphaName", h.CanonicalName())
2022-02-19 11:26:38 +03:00
case *hyphae.MediaHypha:
slog.Info("Serving media file", "path", h.MediaFilePath())
2022-02-19 11:26:38 +03:00
w.Header().Set("Content-Type", mimetype.FromExtension(filepath.Ext(h.MediaFilePath())))
http.ServeFile(w, rq, h.MediaFilePath())
}
}
2021-01-30 23:29:56 +05:00
// 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 template.HTML
openGraph template.HTML
lc = l18n.FromRequest(rq)
meta = viewutil.MetaFrom(w, rq)
subhyphae, prevHyphaName, nextHyphaName = tree.Tree(h.CanonicalName())
cats = categories.CategoriesWithHypha(h.CanonicalName())
category_list = ":" + strings.Join(cats, ":") + ":"
isMyProfile = cfg.UseAuth && util.IsProfileName(h.CanonicalName()) && meta.U.Name == strings.TrimPrefix(h.CanonicalName(), cfg.UserHypha+"/")
data = map[string]any{
"HyphaName": h.CanonicalName(),
"SubhyphaeHTML": subhyphae,
"PrevHyphaName": prevHyphaName,
"NextHyphaName": nextHyphaName,
"IsMyProfile": isMyProfile,
"NaviTitle": hypview.NaviTitle(meta, h.CanonicalName()),
"BacklinkCount": backlinks.BacklinksCount(h.CanonicalName()),
"GivenPermissionToModify": user.CanProceed(rq, "edit"),
"Categories": cats,
"IsMediaHypha": false,
}
)
slog.Info("reading hypha", "name", h.CanonicalName(), "can edit", data["GivenPermissionToModify"])
meta.BodyAttributes = map[string]string{
"cats": category_list,
}
switch h := h.(type) {
case *hyphae.EmptyHypha:
w.WriteHeader(http.StatusNotFound)
data["Contents"] = ""
_ = pageHypha.RenderTo(meta, data)
2022-02-19 11:26:38 +03:00
case hyphae.ExistingHypha:
fileContentsT, err := os.ReadFile(h.TextFilePath())
if err == nil {
2022-06-10 18:45:27 +03:00
ctx, _ := mycocontext.ContextFromStringInput(string(fileContentsT), mycoopts.MarkupOptions(hyphaName))
getOpenGraph, descVisitor, imgVisitor := tools.OpenGraphVisitors(ctx)
openGraph = template.HTML(getOpenGraph())
ast := mycomarkup.BlockTree(ctx, descVisitor, imgVisitor)
contents = template.HTML(mycomarkup.BlocksToHTML(ctx, ast))
}
2022-02-19 11:26:38 +03:00
switch h := h.(type) {
case *hyphae.MediaHypha:
contents = template.HTML(mycoopts.Media(h, lc)) + contents
data["IsMediaHypha"] = true
}
data["Contents"] = contents
meta.HeadElements = append(meta.HeadElements, openGraph)
_ = pageHypha.RenderTo(meta, data)
// TODO: check head cats
// TODO: check opengraph
}
}
// handlerBacklinks lists all backlinks to a hypha.
func handlerBacklinks(w http.ResponseWriter, rq *http.Request) {
hyphaName := util.HyphaNameFromRq(rq, "backlinks")
_ = pageBacklinks.RenderTo(viewutil.MetaFrom(w, rq),
map[string]any{
"Addr": "/backlinks/" + hyphaName,
"HyphaName": hyphaName,
"Backlinks": backlinks.BacklinksFor(hyphaName),
})
}
func handlerOrphans(w http.ResponseWriter, rq *http.Request) {
_ = pageOrphans.RenderTo(viewutil.MetaFrom(w, rq),
map[string]any{
"Addr": "/orphans",
"Orphans": backlinks.Orphans(),
})
}