1
0
mirror of https://github.com/osmarks/mycorrhiza.git synced 2024-12-12 05:20:26 +00:00

Sign edits and refactor mutators a little

This commit is contained in:
bouncepaw 2020-11-18 18:07:53 +05:00
parent 57751d03f4
commit b30c368c48
5 changed files with 93 additions and 73 deletions

View File

@ -8,6 +8,7 @@ import (
"path/filepath" "path/filepath"
"sync" "sync"
"github.com/bouncepaw/mycorrhiza/user"
"github.com/bouncepaw/mycorrhiza/util" "github.com/bouncepaw/mycorrhiza/util"
) )
@ -29,7 +30,7 @@ const (
type HistoryOp struct { type HistoryOp struct {
// All errors are appended here. // All errors are appended here.
Errs []error Errs []error
opType OpType Type OpType
userMsg string userMsg string
name string name string
email string email string
@ -39,8 +40,10 @@ type HistoryOp struct {
func Operation(opType OpType) *HistoryOp { func Operation(opType OpType) *HistoryOp {
gitMutex.Lock() gitMutex.Lock()
hop := &HistoryOp{ hop := &HistoryOp{
Errs: []error{}, Errs: []error{},
opType: opType, name: "anon",
email: "anon@mycorrhiza",
Type: opType,
} }
return hop return hop
} }
@ -116,9 +119,11 @@ func (hop *HistoryOp) WithMsg(userMsg string) *HistoryOp {
return hop return hop
} }
// WithSignature sets a signature for the future commit. You need to pass a username only, the rest is upon us (including email and time). // WithUser sets a user for the commit.
func (hop *HistoryOp) WithSignature(username string) *HistoryOp { func (hop *HistoryOp) WithUser(u *user.User) *HistoryOp {
hop.name = username if u.Group != user.UserAnon {
hop.email = username + "@mycorrhiza" // A fake email, why not hop.name = u.Name
hop.email = u.Name + "@mycorrhiza"
}
return hop return hop
} }

View File

@ -11,12 +11,14 @@ import (
) )
func init() { func init() {
http.HandleFunc("/upload-binary/", handlerUploadBinary) // Those that do not actually mutate anything:
http.HandleFunc("/upload-text/", handlerUploadText)
http.HandleFunc("/edit/", handlerEdit) http.HandleFunc("/edit/", handlerEdit)
http.HandleFunc("/delete-ask/", handlerDeleteAsk) http.HandleFunc("/delete-ask/", handlerDeleteAsk)
http.HandleFunc("/delete-confirm/", handlerDeleteConfirm)
http.HandleFunc("/rename-ask/", handlerRenameAsk) http.HandleFunc("/rename-ask/", handlerRenameAsk)
// 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("/rename-confirm/", handlerRenameConfirm)
} }
@ -38,20 +40,16 @@ func handlerRenameConfirm(w http.ResponseWriter, rq *http.Request) {
log.Println(rq.URL) log.Println(rq.URL)
var ( var (
hyphaName = HyphaNameFromRq(rq, "rename-confirm") hyphaName = HyphaNameFromRq(rq, "rename-confirm")
hyphaData, isOld = HyphaStorage[hyphaName] _, isOld = HyphaStorage[hyphaName]
newName = CanonicalName(rq.PostFormValue("new-name")) newName = CanonicalName(rq.PostFormValue("new-name"))
_, newNameIsUsed = HyphaStorage[newName] _, newNameIsUsed = HyphaStorage[newName]
recursive bool recursive = rq.PostFormValue("recursive") == "true"
u = user.FromRequest(rq).OrAnon()
) )
if ok := user.CanProceed(rq, "rename-confirm"); !ok { switch {
case !u.CanProceed("rename-confirm"):
HttpErr(w, http.StatusForbidden, hyphaName, "Not enough rights", "You must be a trusted editor to rename pages.") HttpErr(w, http.StatusForbidden, hyphaName, "Not enough rights", "You must be a trusted editor to rename pages.")
log.Println("Rejected", rq.URL) log.Println("Rejected", rq.URL)
return
}
if rq.PostFormValue("recursive") == "true" {
recursive = true
}
switch {
case newNameIsUsed: case newNameIsUsed:
HttpErr(w, http.StatusBadRequest, hyphaName, "Error: hypha exists", HttpErr(w, http.StatusBadRequest, hyphaName, "Error: hypha exists",
fmt.Sprintf("Hypha named <a href='/page/%s'>%s</a> already exists.", hyphaName, hyphaName)) fmt.Sprintf("Hypha named <a href='/page/%s'>%s</a> already exists.", hyphaName, hyphaName))
@ -65,12 +63,12 @@ func handlerRenameConfirm(w http.ResponseWriter, rq *http.Request) {
HttpErr(w, http.StatusBadRequest, hyphaName, "Error: invalid name", HttpErr(w, http.StatusBadRequest, hyphaName, "Error: invalid name",
"Invalid new name. Names cannot contain characters <code>^?!:#@&gt;&lt;*|\"\\'&amp;%</code>") "Invalid new name. Names cannot contain characters <code>^?!:#@&gt;&lt;*|\"\\'&amp;%</code>")
default: default:
if hop := hyphaData.RenameHypha(hyphaName, newName, recursive); len(hop.Errs) == 0 { if hop := RenameHypha(hyphaName, newName, recursive, u); len(hop.Errs) != 0 {
http.Redirect(w, rq, "/page/"+newName, http.StatusSeeOther)
} else {
HttpErr(w, http.StatusInternalServerError, hyphaName, HttpErr(w, http.StatusInternalServerError, hyphaName,
"Error: could not rename hypha", "Error: could not rename hypha",
fmt.Sprintf("Could not rename this hypha due to an internal error. Server errors: <code>%v</code>", hop.Errs)) fmt.Sprintf("Could not rename this hypha due to an internal error. Server errors: <code>%v</code>", hop.Errs))
} else {
http.Redirect(w, rq, "/page/"+newName, http.StatusSeeOther)
} }
} }
} }
@ -96,27 +94,27 @@ func handlerDeleteConfirm(w http.ResponseWriter, rq *http.Request) {
var ( var (
hyphaName = HyphaNameFromRq(rq, "delete-confirm") hyphaName = HyphaNameFromRq(rq, "delete-confirm")
hyphaData, isOld = HyphaStorage[hyphaName] hyphaData, isOld = HyphaStorage[hyphaName]
u = user.FromRequest(rq)
) )
if ok := user.CanProceed(rq, "delete-confirm"); !ok { if !u.CanProceed("delete-confirm") {
HttpErr(w, http.StatusForbidden, hyphaName, "Not enough rights", "You must be a moderator to delete pages.") HttpErr(w, http.StatusForbidden, hyphaName, "Not enough rights", "You must be a moderator to delete pages.")
log.Println("Rejected", rq.URL) log.Println("Rejected", rq.URL)
return return
} }
if isOld { if !isOld {
// If deleted successfully
if hop := hyphaData.DeleteHypha(hyphaName); len(hop.Errs) == 0 {
http.Redirect(w, rq, "/page/"+hyphaName, http.StatusSeeOther)
} else {
HttpErr(w, http.StatusInternalServerError, hyphaName,
"Error: could not delete hypha",
fmt.Sprintf("Could not delete this hypha due to an internal error. Server errors: <code>%v</code>", hop.Errs))
}
} else {
// The precondition is to have the hypha in the first place. // The precondition is to have the hypha in the first place.
HttpErr(w, http.StatusPreconditionFailed, hyphaName, HttpErr(w, http.StatusPreconditionFailed, hyphaName,
"Error: no such hypha", "Error: no such hypha",
"Could not delete this hypha because it does not exist.") "Could not delete this hypha because it does not exist.")
return
} }
if hop := hyphaData.DeleteHypha(hyphaName, u); len(hop.Errs) != 0 {
HttpErr(w, http.StatusInternalServerError, hyphaName,
"Error: could not delete hypha",
fmt.Sprintf("Could not delete this hypha due to internal errors. Server errors: <code>%v</code>", hop.Errs))
return
}
http.Redirect(w, rq, "/page/"+hyphaName, http.StatusSeeOther)
} }
// handlerEdit shows the edit form. It doesn't edit anything actually. // handlerEdit shows the edit form. It doesn't edit anything actually.
@ -151,23 +149,20 @@ func handlerEdit(w http.ResponseWriter, rq *http.Request) {
func handlerUploadText(w http.ResponseWriter, rq *http.Request) { func handlerUploadText(w http.ResponseWriter, rq *http.Request) {
log.Println(rq.URL) log.Println(rq.URL)
var ( var (
hyphaName = HyphaNameFromRq(rq, "upload-text") hyphaName = HyphaNameFromRq(rq, "upload-text")
hyphaData, isOld = HyphaStorage[hyphaName] textData = rq.PostFormValue("text")
textData = rq.PostFormValue("text") u = user.FromRequest(rq).OrAnon()
) )
if ok := user.CanProceed(rq, "upload-text"); !ok { if ok := user.CanProceed(rq, "upload-text"); !ok {
HttpErr(w, http.StatusForbidden, hyphaName, "Not enough rights", "You must be an editor to edit pages.") HttpErr(w, http.StatusForbidden, hyphaName, "Not enough rights", "You must be an editor to edit pages.")
log.Println("Rejected", rq.URL) log.Println("Rejected", rq.URL)
return return
} }
if !isOld {
hyphaData = &HyphaData{}
}
if textData == "" { if textData == "" {
HttpErr(w, http.StatusBadRequest, hyphaName, "Error", "No text data passed") HttpErr(w, http.StatusBadRequest, hyphaName, "Error", "No text data passed")
return return
} }
if hop := hyphaData.UploadText(hyphaName, textData, isOld); len(hop.Errs) != 0 { if hop := UploadText(hyphaName, textData, u); len(hop.Errs) != 0 {
HttpErr(w, http.StatusInternalServerError, hyphaName, "Error", hop.Errs[0].Error()) HttpErr(w, http.StatusInternalServerError, hyphaName, "Error", hop.Errs[0].Error())
} else { } else {
http.Redirect(w, rq, "/page/"+hyphaName, http.StatusSeeOther) http.Redirect(w, rq, "/page/"+hyphaName, http.StatusSeeOther)
@ -177,36 +172,37 @@ func handlerUploadText(w http.ResponseWriter, rq *http.Request) {
// handlerUploadBinary uploads a new binary part for the hypha. // handlerUploadBinary uploads a new binary part for the hypha.
func handlerUploadBinary(w http.ResponseWriter, rq *http.Request) { func handlerUploadBinary(w http.ResponseWriter, rq *http.Request) {
log.Println(rq.URL) log.Println(rq.URL)
hyphaName := HyphaNameFromRq(rq, "upload-binary") var (
if ok := user.CanProceed(rq, "upload-binary"); !ok { hyphaName = HyphaNameFromRq(rq, "upload-binary")
u = user.FromRequest(rq)
)
if !u.CanProceed("upload-binary") {
HttpErr(w, http.StatusForbidden, hyphaName, "Not enough rights", "You must be an editor to upload attachments.") HttpErr(w, http.StatusForbidden, hyphaName, "Not enough rights", "You must be an editor to upload attachments.")
log.Println("Rejected", rq.URL) log.Println("Rejected", rq.URL)
return return
} }
rq.ParseMultipartForm(10 << 20)
rq.ParseMultipartForm(10 << 20) // Set upload limit
file, handler, err := rq.FormFile("binary") file, handler, err := rq.FormFile("binary")
if file != nil { if file != nil {
defer file.Close() defer file.Close()
} }
// If file is not passed: // If file is not passed:
if err != nil { if err != nil {
HttpErr(w, http.StatusBadRequest, hyphaName, "Error", "No binary data passed") HttpErr(w, http.StatusBadRequest, hyphaName, "Error", "No binary data passed")
return return
} }
// If file is passed: // If file is passed:
var ( var (
hyphaData, isOld = HyphaStorage[hyphaName] mime = handler.Header.Get("Content-Type")
mime = handler.Header.Get("Content-Type") hop = UploadBinary(hyphaName, mime, file, u)
) )
if !isOld {
hyphaData = &HyphaData{}
}
hop := hyphaData.UploadBinary(hyphaName, mime, file, isOld)
if len(hop.Errs) != 0 { if len(hop.Errs) != 0 {
HttpErr(w, http.StatusInternalServerError, hyphaName, "Error", hop.Errs[0].Error()) HttpErr(w, http.StatusInternalServerError, hyphaName, "Error", hop.Errs[0].Error())
} else { return
http.Redirect(w, rq, "/page/"+hyphaName, http.StatusSeeOther)
} }
http.Redirect(w, rq, "/page/"+hyphaName, http.StatusSeeOther)
} }

View File

@ -12,6 +12,7 @@ import (
"github.com/bouncepaw/mycorrhiza/history" "github.com/bouncepaw/mycorrhiza/history"
"github.com/bouncepaw/mycorrhiza/markup" "github.com/bouncepaw/mycorrhiza/markup"
"github.com/bouncepaw/mycorrhiza/user"
"github.com/bouncepaw/mycorrhiza/util" "github.com/bouncepaw/mycorrhiza/util"
) )
@ -33,6 +34,15 @@ func init() {
} }
} }
// GetHyphaData finds a hypha addressed by `hyphaName` and returns its `hyphaData`. `hyphaData` is set to a zero value if this hypha does not exist. `isOld` is false if this hypha does not exist.
func GetHyphaData(hyphaName string) (hyphaData *HyphaData, isOld bool) {
hyphaData, isOld = HyphaStorage[hyphaName]
if hyphaData == nil {
hyphaData = &HyphaData{}
}
return
}
// HyphaData represents a hypha's meta information: binary and text parts rooted paths and content types. // HyphaData represents a hypha's meta information: binary and text parts rooted paths and content types.
type HyphaData struct { type HyphaData struct {
textPath string textPath string
@ -40,10 +50,16 @@ type HyphaData struct {
} }
// uploadHelp is a helper function for UploadText and UploadBinary // uploadHelp is a helper function for UploadText and UploadBinary
func (hd *HyphaData) uploadHelp(hop *history.HistoryOp, hyphaName, ext string, originalFullPath *string, isOld bool, data []byte) *history.HistoryOp { func uploadHelp(hop *history.HistoryOp, hyphaName, ext string, data []byte, u *user.User) *history.HistoryOp {
var ( var (
fullPath = filepath.Join(WikiDir, hyphaName+ext) hyphaData, isOld = GetHyphaData(hyphaName)
fullPath = filepath.Join(WikiDir, hyphaName+ext)
originalFullPath = &hyphaData.textPath
) )
if hop.Type == history.TypeEditBinary {
originalFullPath = &hyphaData.binaryPath
}
if err := os.MkdirAll(filepath.Dir(fullPath), 0777); err != nil { if err := os.MkdirAll(filepath.Dir(fullPath), 0777); err != nil {
return hop.WithError(err) return hop.WithError(err)
} }
@ -51,30 +67,34 @@ func (hd *HyphaData) uploadHelp(hop *history.HistoryOp, hyphaName, ext string, o
if err := ioutil.WriteFile(fullPath, data, 0644); err != nil { if err := ioutil.WriteFile(fullPath, data, 0644); err != nil {
return hop.WithError(err) return hop.WithError(err)
} }
if isOld && *originalFullPath != fullPath && *originalFullPath != "" { if isOld && *originalFullPath != fullPath && *originalFullPath != "" {
if err := history.Rename(*originalFullPath, fullPath); err != nil { if err := history.Rename(*originalFullPath, fullPath); err != nil {
return hop.WithError(err) return hop.WithError(err)
} }
log.Println("Move", *originalFullPath, "to", fullPath) log.Println("Move", *originalFullPath, "to", fullPath)
} }
// New hyphae must be added to the hypha storage
if !isOld { if !isOld {
HyphaStorage[hyphaName] = hd HyphaStorage[hyphaName] = hyphaData
} }
*originalFullPath = fullPath *originalFullPath = fullPath
log.Printf("%v\n", *hd)
return hop.WithFiles(fullPath). return hop.WithFiles(fullPath).
WithSignature("anon"). WithUser(u).
Apply() Apply()
} }
// UploadText loads a new text part from `textData` for hypha `hyphaName` with `hd`. It must be marked if the hypha `isOld`. // UploadText loads a new text part from `textData` for hypha `hyphaName`.
func (hd *HyphaData) UploadText(hyphaName, textData string, isOld bool) *history.HistoryOp { func UploadText(hyphaName, textData string, u *user.User) *history.HistoryOp {
hop := history.Operation(history.TypeEditText).WithMsg(fmt.Sprintf("Edit %s", hyphaName)) return uploadHelp(
return hd.uploadHelp(hop, hyphaName, ".myco", &hd.textPath, isOld, []byte(textData)) history.
Operation(history.TypeEditText).
WithMsg(fmt.Sprintf("Edit %s", hyphaName)),
hyphaName, ".myco", []byte(textData), u)
} }
// UploadBinary loads a new binary part from `file` for hypha `hyphaName` with `hd`. The contents have the specified `mime` type. It must be marked if the hypha `isOld`. // UploadBinary loads a new binary part from `file` for hypha `hyphaName` with `hd`. The contents have the specified `mime` type. It must be marked if the hypha `isOld`.
func (hd *HyphaData) UploadBinary(hyphaName, mime string, file multipart.File, isOld bool) *history.HistoryOp { func UploadBinary(hyphaName, mime string, file multipart.File, u *user.User) *history.HistoryOp {
var ( var (
hop = history.Operation(history.TypeEditBinary).WithMsg(fmt.Sprintf("Upload binary part for %s with type %s", hyphaName, mime)) hop = history.Operation(history.TypeEditBinary).WithMsg(fmt.Sprintf("Upload binary part for %s with type %s", hyphaName, mime))
data, err = ioutil.ReadAll(file) data, err = ioutil.ReadAll(file)
@ -82,16 +102,15 @@ func (hd *HyphaData) UploadBinary(hyphaName, mime string, file multipart.File, i
if err != nil { if err != nil {
return hop.WithError(err).Apply() return hop.WithError(err).Apply()
} }
return uploadHelp(hop, hyphaName, MimeToExtension(mime), data, u)
return hd.uploadHelp(hop, hyphaName, MimeToExtension(mime), &hd.binaryPath, isOld, data)
} }
// DeleteHypha deletes hypha and makes a history record about that. // DeleteHypha deletes hypha and makes a history record about that.
func (hd *HyphaData) DeleteHypha(hyphaName string) *history.HistoryOp { func (hd *HyphaData) DeleteHypha(hyphaName string, u *user.User) *history.HistoryOp {
hop := history.Operation(history.TypeDeleteHypha). hop := history.Operation(history.TypeDeleteHypha).
WithFilesRemoved(hd.textPath, hd.binaryPath). WithFilesRemoved(hd.textPath, hd.binaryPath).
WithMsg(fmt.Sprintf("Delete %s", hyphaName)). WithMsg(fmt.Sprintf("Delete %s", hyphaName)).
WithSignature("anon"). WithUser(u).
Apply() Apply()
if len(hop.Errs) == 0 { if len(hop.Errs) == 0 {
delete(HyphaStorage, hyphaName) delete(HyphaStorage, hyphaName)
@ -138,7 +157,7 @@ func relocateHyphaData(hyphaNames []string, replaceName func(string) string) {
} }
// RenameHypha renames hypha from old name `hyphaName` to `newName` and makes a history record about that. If `recursive` is `true`, its subhyphae will be renamed the same way. // RenameHypha renames hypha from old name `hyphaName` to `newName` and makes a history record about that. If `recursive` is `true`, its subhyphae will be renamed the same way.
func (hd *HyphaData) RenameHypha(hyphaName, newName string, recursive bool) *history.HistoryOp { func RenameHypha(hyphaName, newName string, recursive bool, u *user.User) *history.HistoryOp {
var ( var (
replaceName = func(str string) string { replaceName = func(str string) string {
return strings.Replace(str, hyphaName, newName, 1) return strings.Replace(str, hyphaName, newName, 1)
@ -157,7 +176,7 @@ func (hd *HyphaData) RenameHypha(hyphaName, newName string, recursive bool) *his
} }
hop.WithFilesRenamed(renameMap). hop.WithFilesRenamed(renameMap).
WithMsg(fmt.Sprintf(renameMsg, hyphaName, newName)). WithMsg(fmt.Sprintf(renameMsg, hyphaName, newName)).
WithSignature("anon"). WithUser(u).
Apply() Apply()
if len(hop.Errs) == 0 { if len(hop.Errs) == 0 {
relocateHyphaData(hyphaNames, replaceName) relocateHyphaData(hyphaNames, replaceName)

View File

@ -62,9 +62,9 @@ func (ug UserGroup) CanAccessRoute(route string) bool {
} }
func CanProceed(rq *http.Request, route string) bool { func CanProceed(rq *http.Request, route string) bool {
ug := UserAnon return FromRequest(rq).OrAnon().CanProceed(route)
if u := FromRequest(rq); u != nil { }
ug = u.Group
} func (u *User) CanProceed(route string) bool {
return ug.CanAccessRoute(route) return u.Group.CanAccessRoute(route)
} }

View File

@ -44,7 +44,7 @@ func FromRequest(rq *http.Request) *User {
if err != nil { if err != nil {
return nil return nil
} }
return UserStorage.userByToken(cookie.Value) return UserStorage.userByToken(cookie.Value).OrAnon()
} }
func LoginDataHTTP(w http.ResponseWriter, rq *http.Request, username, password string) string { func LoginDataHTTP(w http.ResponseWriter, rq *http.Request, username, password string) string {