mirror of
https://github.com/osmarks/mycorrhiza.git
synced 2025-01-19 07:02:51 +00:00
commit
54fdb29ddf
@ -2,6 +2,8 @@
|
||||
|
||||
<img src="https://mycorrhiza.wiki/binary/release/1.4/screenshot" alt="A screenshot of mycorrhiza.wiki's home page in the Safari browser" width="600">
|
||||
|
||||
[![Go Report Card](https://goreportcard.com/badge/github.com/bouncepaw/mycorrhiza)](https://goreportcard.com/report/github.com/bouncepaw/mycorrhiza)
|
||||
|
||||
**Mycorrhiza Wiki** is a lightweight file-system wiki engine that uses Git for keeping history.
|
||||
|
||||
[👉 Main wiki](https://mycorrhiza.wiki)
|
||||
|
1
flag.go
1
flag.go
@ -28,6 +28,7 @@ func printHelp() {
|
||||
flag.PrintDefaults()
|
||||
}
|
||||
|
||||
// CreateUserCommand is parameters for admin creation flag. Only a single parameter, though.
|
||||
type CreateUserCommand struct {
|
||||
name string
|
||||
}
|
||||
|
@ -33,6 +33,7 @@ func Start() {
|
||||
gitpath = path
|
||||
}
|
||||
|
||||
// InitGitRepo checks a Git repository and initializes it if necessary.
|
||||
func InitGitRepo() {
|
||||
// Detect if the Git repo directory is a Git repository
|
||||
isGitRepo := true
|
||||
@ -144,7 +145,7 @@ func (rev *Revision) textDiff() (diff string) {
|
||||
for _, filename := range filenames {
|
||||
text, err := PrimitiveDiffAtRevision(filename, rev.Hash)
|
||||
if err != nil {
|
||||
diff += "\nAn error has occured with " + filename + "\n"
|
||||
diff += "\nAn error has occurred with " + filename + "\n"
|
||||
}
|
||||
diff += text + "\n"
|
||||
}
|
||||
|
@ -52,18 +52,22 @@ func recentChangesFeed() *feeds.Feed {
|
||||
return feed
|
||||
}
|
||||
|
||||
// RecentChangesRSS creates recent changes feed in RSS format.
|
||||
func RecentChangesRSS() (string, error) {
|
||||
return recentChangesFeed().ToRss()
|
||||
}
|
||||
|
||||
// RecentChangesAtom creates recent changes feed in Atom format.
|
||||
func RecentChangesAtom() (string, error) {
|
||||
return recentChangesFeed().ToAtom()
|
||||
}
|
||||
|
||||
// RecentChangesJSON creates recent changes feed in JSON format.
|
||||
func RecentChangesJSON() (string, error) {
|
||||
return recentChangesFeed().ToJSON()
|
||||
}
|
||||
|
||||
// RecentChanges gathers an arbitrary number of latest changes in form of revisions slice.
|
||||
func RecentChanges(n int) []Revision {
|
||||
var (
|
||||
out, err = silentGitsh(
|
||||
@ -110,8 +114,8 @@ func Revisions(hyphaName string) ([]Revision, error) {
|
||||
return revs, err
|
||||
}
|
||||
|
||||
// HistoryWithRevisions returns an html representation of `revs` that is meant to be inserted in a history page.
|
||||
func HistoryWithRevisions(hyphaName string, revs []Revision) (html string) {
|
||||
// WithRevisions returns an html representation of `revs` that is meant to be inserted in a history page.
|
||||
func WithRevisions(hyphaName string, revs []Revision) (html string) {
|
||||
var (
|
||||
currentYear int
|
||||
currentMonth time.Month
|
||||
|
@ -19,16 +19,22 @@ var gitMutex = sync.Mutex{}
|
||||
type OpType int
|
||||
|
||||
const (
|
||||
// TypeNone represents an empty operation. Not to be used in practice.
|
||||
TypeNone OpType = iota
|
||||
// TypeEditText represents an edit of hypha text part.
|
||||
TypeEditText
|
||||
// TypeEditBinary represents an addition or replacement of hypha attachment.
|
||||
TypeEditBinary
|
||||
// TypeDeleteHypha represents a hypha deletion
|
||||
TypeDeleteHypha
|
||||
// TypeRenameHypha represents a hypha renaming
|
||||
TypeRenameHypha
|
||||
// TypeUnattachHypha represents a hypha attachment deletion
|
||||
TypeUnattachHypha
|
||||
)
|
||||
|
||||
// HistoryOp is an object representing a history operation.
|
||||
type HistoryOp struct {
|
||||
// Op is an object representing a history operation.
|
||||
type Op struct {
|
||||
// All errors are appended here.
|
||||
Errs []error
|
||||
Type OpType
|
||||
@ -38,9 +44,9 @@ type HistoryOp struct {
|
||||
}
|
||||
|
||||
// Operation is a constructor of a history operation.
|
||||
func Operation(opType OpType) *HistoryOp {
|
||||
func Operation(opType OpType) *Op {
|
||||
gitMutex.Lock()
|
||||
hop := &HistoryOp{
|
||||
hop := &Op{
|
||||
Errs: []error{},
|
||||
name: "anon",
|
||||
email: "anon@mycorrhiza",
|
||||
@ -50,7 +56,7 @@ func Operation(opType OpType) *HistoryOp {
|
||||
}
|
||||
|
||||
// git operation maker helper
|
||||
func (hop *HistoryOp) gitop(args ...string) *HistoryOp {
|
||||
func (hop *Op) gitop(args ...string) *Op {
|
||||
out, err := gitsh(args...)
|
||||
if err != nil {
|
||||
fmt.Println("out:", out.String())
|
||||
@ -60,17 +66,18 @@ func (hop *HistoryOp) gitop(args ...string) *HistoryOp {
|
||||
}
|
||||
|
||||
// WithErr appends the `err` to the list of errors.
|
||||
func (hop *HistoryOp) WithErr(err error) *HistoryOp {
|
||||
func (hop *Op) WithErr(err error) *Op {
|
||||
hop.Errs = append(hop.Errs, err)
|
||||
return hop
|
||||
}
|
||||
|
||||
func (hop *HistoryOp) WithErrAbort(err error) *HistoryOp {
|
||||
// WithErrAbort appends the `err` to the list of errors and immediately aborts the operation.
|
||||
func (hop *Op) WithErrAbort(err error) *Op {
|
||||
return hop.WithErr(err).Abort()
|
||||
}
|
||||
|
||||
// WithFilesRemoved git-rm-s all passed `paths`. Paths can be rooted or not. Paths that are empty strings are ignored.
|
||||
func (hop *HistoryOp) WithFilesRemoved(paths ...string) *HistoryOp {
|
||||
func (hop *Op) WithFilesRemoved(paths ...string) *Op {
|
||||
args := []string{"rm", "--quiet", "--"}
|
||||
for _, path := range paths {
|
||||
if path != "" {
|
||||
@ -81,7 +88,7 @@ func (hop *HistoryOp) WithFilesRemoved(paths ...string) *HistoryOp {
|
||||
}
|
||||
|
||||
// WithFilesRenamed git-mv-s all passed keys of `pairs` to values of `pairs`. Paths can be rooted ot not. Empty keys are ignored.
|
||||
func (hop *HistoryOp) WithFilesRenamed(pairs map[string]string) *HistoryOp {
|
||||
func (hop *Op) WithFilesRenamed(pairs map[string]string) *Op {
|
||||
for from, to := range pairs {
|
||||
if from != "" {
|
||||
if err := os.MkdirAll(filepath.Dir(to), 0777); err != nil {
|
||||
@ -97,7 +104,7 @@ func (hop *HistoryOp) WithFilesRenamed(pairs map[string]string) *HistoryOp {
|
||||
}
|
||||
|
||||
// WithFiles stages all passed `paths`. Paths can be rooted or not.
|
||||
func (hop *HistoryOp) WithFiles(paths ...string) *HistoryOp {
|
||||
func (hop *Op) WithFiles(paths ...string) *Op {
|
||||
for i, path := range paths {
|
||||
paths[i] = util.ShorterPath(path)
|
||||
}
|
||||
@ -106,7 +113,7 @@ func (hop *HistoryOp) WithFiles(paths ...string) *HistoryOp {
|
||||
}
|
||||
|
||||
// Apply applies history operation by doing the commit.
|
||||
func (hop *HistoryOp) Apply() *HistoryOp {
|
||||
func (hop *Op) Apply() *Op {
|
||||
hop.gitop(
|
||||
"commit",
|
||||
"--author='"+hop.name+" <"+hop.email+">'",
|
||||
@ -117,13 +124,13 @@ func (hop *HistoryOp) Apply() *HistoryOp {
|
||||
}
|
||||
|
||||
// Abort aborts the history operation.
|
||||
func (hop *HistoryOp) Abort() *HistoryOp {
|
||||
func (hop *Op) Abort() *Op {
|
||||
gitMutex.Unlock()
|
||||
return hop
|
||||
}
|
||||
|
||||
// WithMsg sets what message will be used for the future commit. If user message exceeds one line, it is stripped down.
|
||||
func (hop *HistoryOp) WithMsg(userMsg string) *HistoryOp {
|
||||
func (hop *Op) WithMsg(userMsg string) *Op {
|
||||
for _, ch := range userMsg {
|
||||
if ch == '\r' || ch == '\n' {
|
||||
break
|
||||
@ -134,7 +141,7 @@ func (hop *HistoryOp) WithMsg(userMsg string) *HistoryOp {
|
||||
}
|
||||
|
||||
// WithUser sets a user for the commit.
|
||||
func (hop *HistoryOp) WithUser(u *user.User) *HistoryOp {
|
||||
func (hop *Op) WithUser(u *user.User) *Op {
|
||||
if u.Group != "anon" {
|
||||
hop.name = u.Name
|
||||
hop.email = u.Name + "@mycorrhiza"
|
||||
@ -142,10 +149,12 @@ func (hop *HistoryOp) WithUser(u *user.User) *HistoryOp {
|
||||
return hop
|
||||
}
|
||||
|
||||
func (hop *HistoryOp) HasErrors() bool {
|
||||
// HasErrors checks whether operation has errors appended.
|
||||
func (hop *Op) HasErrors() bool {
|
||||
return len(hop.Errs) > 0
|
||||
}
|
||||
|
||||
func (hop *HistoryOp) FirstErrorText() string {
|
||||
// FirstErrorText extracts first error appended to the operation.
|
||||
func (hop *Op) FirstErrorText() string {
|
||||
return hop.Errs[0].Error()
|
||||
}
|
||||
|
@ -38,12 +38,14 @@ type BacklinkIndexOperation interface {
|
||||
Apply()
|
||||
}
|
||||
|
||||
// BacklinkIndexEdit contains data for backlink index update after a hypha edit
|
||||
type BacklinkIndexEdit struct {
|
||||
Name string
|
||||
OldLinks []string
|
||||
NewLinks []string
|
||||
}
|
||||
|
||||
// Apply changes backlink index respective to the operation data
|
||||
func (op BacklinkIndexEdit) Apply() {
|
||||
oldLinks := toLinkSet(op.OldLinks)
|
||||
newLinks := toLinkSet(op.NewLinks)
|
||||
@ -62,11 +64,13 @@ func (op BacklinkIndexEdit) Apply() {
|
||||
}
|
||||
}
|
||||
|
||||
// BacklinkIndexDeletion contains data for backlink index update after a hypha deletion
|
||||
type BacklinkIndexDeletion struct {
|
||||
Name string
|
||||
Links []string
|
||||
}
|
||||
|
||||
// Apply changes backlink index respective to the operation data
|
||||
func (op BacklinkIndexDeletion) Apply() {
|
||||
for _, link := range op.Links {
|
||||
if lSet, exists := backlinkIndex[link]; exists {
|
||||
@ -75,12 +79,14 @@ func (op BacklinkIndexDeletion) Apply() {
|
||||
}
|
||||
}
|
||||
|
||||
// BacklinkIndexRenaming contains data for backlink index update after a hypha renaming
|
||||
type BacklinkIndexRenaming struct {
|
||||
OldName string
|
||||
NewName string
|
||||
Links []string
|
||||
}
|
||||
|
||||
// Apply changes backlink index respective to the operation data
|
||||
func (op BacklinkIndexRenaming) Apply() {
|
||||
for _, link := range op.Links {
|
||||
if lSet, exists := backlinkIndex[link]; exists {
|
||||
|
@ -10,21 +10,21 @@ var count = struct {
|
||||
sync.Mutex
|
||||
}{}
|
||||
|
||||
// Set the value of hyphae count to zero. Use when reloading hyphae.
|
||||
// ResetCount sets the value of hyphae count to zero. Use when reloading hyphae.
|
||||
func ResetCount() {
|
||||
count.Lock()
|
||||
count.value = 0
|
||||
count.Unlock()
|
||||
}
|
||||
|
||||
// Increment the value of the hyphae counter. Use when creating new hyphae or loading hyphae from disk.
|
||||
// IncrementCount increments the value of the hyphae counter. Use when creating new hyphae or loading hyphae from disk.
|
||||
func IncrementCount() {
|
||||
count.Lock()
|
||||
count.value++
|
||||
count.Unlock()
|
||||
}
|
||||
|
||||
// Decrement the value of the hyphae counter. Use when deleting existing hyphae.
|
||||
// DecrementCount decrements the value of the hyphae counter. Use when deleting existing hyphae.
|
||||
func DecrementCount() {
|
||||
count.Lock()
|
||||
count.value--
|
||||
|
@ -12,6 +12,7 @@ import (
|
||||
// HyphaPattern is a pattern which all hyphae must match.
|
||||
var HyphaPattern = regexp.MustCompile(`[^?!:#@><*|"'&%{}]+`)
|
||||
|
||||
// Hypha keeps vital information about a hypha
|
||||
type Hypha struct {
|
||||
sync.RWMutex
|
||||
|
||||
@ -79,6 +80,7 @@ func (h *Hypha) Insert() (justRecorded bool) {
|
||||
return !recorded
|
||||
}
|
||||
|
||||
// InsertIfNew checks whether hypha exists and returns `true` if it didn't and has been created.
|
||||
func (h *Hypha) InsertIfNew() (justRecorded bool) {
|
||||
if !h.Exists {
|
||||
return h.Insert()
|
||||
@ -86,6 +88,7 @@ func (h *Hypha) InsertIfNew() (justRecorded bool) {
|
||||
return false
|
||||
}
|
||||
|
||||
// Delete removes a hypha from the storage.
|
||||
func (h *Hypha) Delete() {
|
||||
byNamesMutex.Lock()
|
||||
h.Lock()
|
||||
@ -95,6 +98,7 @@ func (h *Hypha) Delete() {
|
||||
h.Unlock()
|
||||
}
|
||||
|
||||
// RenameTo renames a hypha and performs respective changes in the storage.
|
||||
func (h *Hypha) RenameTo(newName string) {
|
||||
byNamesMutex.Lock()
|
||||
h.Lock()
|
||||
|
@ -11,6 +11,7 @@ type Iteration struct {
|
||||
checks []func(h *Hypha) CheckResult
|
||||
}
|
||||
|
||||
// NewIteration constructs an iteration without checks.
|
||||
func NewIteration() *Iteration {
|
||||
return &Iteration{
|
||||
iterator: YieldExistingHyphae,
|
||||
|
@ -1,6 +1,7 @@
|
||||
// File `iterators.go` contains stuff that iterates over hyphae.
|
||||
package hyphae
|
||||
|
||||
// File `iterators.go` contains stuff that iterates over hyphae.
|
||||
|
||||
import (
|
||||
"sort"
|
||||
"strings"
|
||||
|
@ -14,7 +14,7 @@ var locales = language.NewMatcher([]language.Tag{
|
||||
language.Make("ru"),
|
||||
})
|
||||
|
||||
// GetLocalizer takes a HTTP request and picks the most appropriate localizer (with English fallback)
|
||||
// FromRequest takes a HTTP request and picks the most appropriate localizer (with English fallback)
|
||||
func FromRequest(r *http.Request) *Localizer {
|
||||
t, _, _ := language.ParseAcceptLanguage(r.Header.Get("Accept-Language"))
|
||||
tag, _, _ := locales.Match(t...)
|
||||
@ -91,7 +91,7 @@ func getLocalizationKey(locale string, key string) string {
|
||||
|
||||
/* chekoopa: Missing translation features:
|
||||
- history records (they use Git description, the possible solution is to parse and translate)
|
||||
- history dates (HistoryWithRevisions doesn't consider locale, Monday package is bad idea)
|
||||
- history dates (history.WithRevisions doesn't consider locale, Monday package is bad idea)
|
||||
- probably error messages (which are scattered across the code)
|
||||
- default top bar (it is static from one-shot cfg.SetDefaultHeaderLinks, but it is possible to track default-ness in templates)
|
||||
- alt solution is implementing "special" links
|
||||
|
@ -14,29 +14,30 @@ func canFactory(
|
||||
noRightsMsg string,
|
||||
notExistsMsg string,
|
||||
careAboutExistence bool,
|
||||
) func(*user.User, *hyphae.Hypha) (error, string) {
|
||||
return func(u *user.User, h *hyphae.Hypha) (error, string) {
|
||||
) func(*user.User, *hyphae.Hypha) (string, error) {
|
||||
return func(u *user.User, h *hyphae.Hypha) (string, error) {
|
||||
if !u.CanProceed(action) {
|
||||
rejectLogger(h, u, "no rights")
|
||||
return errors.New(noRightsMsg), "Not enough rights"
|
||||
return "Not enough rights", errors.New(noRightsMsg)
|
||||
}
|
||||
|
||||
if careAboutExistence && !h.Exists {
|
||||
rejectLogger(h, u, "does not exist")
|
||||
return errors.New(notExistsMsg), "Does not exist"
|
||||
return "Does not exist", errors.New(notExistsMsg)
|
||||
}
|
||||
|
||||
if dispatcher == nil {
|
||||
return nil, ""
|
||||
return "", nil
|
||||
}
|
||||
errmsg, errtitle := dispatcher(h, u)
|
||||
if errtitle == "" {
|
||||
return nil, ""
|
||||
return "", nil
|
||||
}
|
||||
return errors.New(errmsg), errtitle
|
||||
return errtitle, errors.New(errmsg)
|
||||
}
|
||||
}
|
||||
|
||||
// CanDelete and etc are hyphae operation checkers based on user rights and hyphae existence.
|
||||
var (
|
||||
CanDelete = canFactory(
|
||||
rejectDeleteLog,
|
||||
|
@ -9,10 +9,10 @@ import (
|
||||
)
|
||||
|
||||
// DeleteHypha deletes hypha and makes a history record about that.
|
||||
func DeleteHypha(u *user.User, h *hyphae.Hypha) (hop *history.HistoryOp, errtitle string) {
|
||||
func DeleteHypha(u *user.User, h *hyphae.Hypha) (hop *history.Op, errtitle string) {
|
||||
hop = history.Operation(history.TypeDeleteHypha)
|
||||
|
||||
if err, errtitle := CanDelete(u, h); errtitle != "" {
|
||||
if errtitle, err := CanDelete(u, h); errtitle != "" {
|
||||
hop.WithErrAbort(err)
|
||||
return hop, errtitle
|
||||
}
|
||||
|
@ -11,36 +11,36 @@ import (
|
||||
"github.com/bouncepaw/mycorrhiza/util"
|
||||
)
|
||||
|
||||
func canRenameThisToThat(oh *hyphae.Hypha, nh *hyphae.Hypha, u *user.User) (err error, errtitle string) {
|
||||
func canRenameThisToThat(oh *hyphae.Hypha, nh *hyphae.Hypha, u *user.User) (errtitle string, err error) {
|
||||
if nh.Exists {
|
||||
rejectRenameLog(oh, u, fmt.Sprintf("name ‘%s’ taken already", nh.Name))
|
||||
return errors.New(fmt.Sprintf("Hypha named <a href='/hypha/%[1]s'>%[1]s</a> already exists, cannot rename", nh.Name)), "Name taken"
|
||||
return "Name taken", fmt.Errorf("Hypha named <a href='/hypha/%[1]s'>%[1]s</a> already exists, cannot rename", nh.Name)
|
||||
}
|
||||
|
||||
if nh.Name == "" {
|
||||
rejectRenameLog(oh, u, "no new name given")
|
||||
return errors.New("No new name is given"), "No name given"
|
||||
return "No name given", errors.New("No new name is given")
|
||||
}
|
||||
|
||||
if !hyphae.HyphaPattern.MatchString(nh.Name) {
|
||||
rejectRenameLog(oh, u, fmt.Sprintf("new name ‘%s’ invalid", nh.Name))
|
||||
return errors.New("Invalid new name. Names cannot contain characters <code>^?!:#@><*|\"\\'&%</code>"), "Invalid name"
|
||||
return "Invalid name", errors.New("Invalid new name. Names cannot contain characters <code>^?!:#@><*|\"\\'&%</code>")
|
||||
}
|
||||
|
||||
return nil, ""
|
||||
return "", nil
|
||||
}
|
||||
|
||||
// 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 RenameHypha(h *hyphae.Hypha, newHypha *hyphae.Hypha, recursive bool, u *user.User) (hop *history.HistoryOp, errtitle string) {
|
||||
func RenameHypha(h *hyphae.Hypha, newHypha *hyphae.Hypha, recursive bool, u *user.User) (hop *history.Op, errtitle string) {
|
||||
newHypha.Lock()
|
||||
defer newHypha.Unlock()
|
||||
hop = history.Operation(history.TypeRenameHypha)
|
||||
|
||||
if err, errtitle := CanRename(u, h); errtitle != "" {
|
||||
if errtitle, err := CanRename(u, h); errtitle != "" {
|
||||
hop.WithErrAbort(err)
|
||||
return hop, errtitle
|
||||
}
|
||||
if err, errtitle := canRenameThisToThat(h, newHypha, u); errtitle != "" {
|
||||
if errtitle, err := canRenameThisToThat(h, newHypha, u); errtitle != "" {
|
||||
hop.WithErrAbort(err)
|
||||
return hop, errtitle
|
||||
}
|
||||
|
@ -1,7 +1,6 @@
|
||||
package shroom
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"github.com/bouncepaw/mycorrhiza/history"
|
||||
@ -10,10 +9,10 @@ import (
|
||||
)
|
||||
|
||||
// UnattachHypha unattaches hypha and makes a history record about that.
|
||||
func UnattachHypha(u *user.User, h *hyphae.Hypha) (hop *history.HistoryOp, errtitle string) {
|
||||
func UnattachHypha(u *user.User, h *hyphae.Hypha) (hop *history.Op, errtitle string) {
|
||||
hop = history.Operation(history.TypeUnattachHypha)
|
||||
|
||||
if err, errtitle := CanUnattach(u, h); errtitle != "" {
|
||||
if errtitle, err := CanUnattach(u, h); errtitle != "" {
|
||||
hop.WithErrAbort(err)
|
||||
return hop, errtitle
|
||||
}
|
||||
@ -27,7 +26,7 @@ func UnattachHypha(u *user.User, h *hyphae.Hypha) (hop *history.HistoryOp, errti
|
||||
if len(hop.Errs) > 0 {
|
||||
rejectUnattachLog(h, u, "fail")
|
||||
// FIXME: something may be wrong here
|
||||
return hop.WithErrAbort(errors.New(fmt.Sprintf("Could not unattach this hypha due to internal server errors: <code>%v</code>", hop.Errs))), "Error"
|
||||
return hop.WithErrAbort(fmt.Errorf("Could not unattach this hypha due to internal server errors: <code>%v</code>", hop.Errs)), "Error"
|
||||
}
|
||||
|
||||
if h.BinaryPath != "" {
|
||||
|
@ -18,7 +18,8 @@ import (
|
||||
"github.com/bouncepaw/mycorrhiza/user"
|
||||
)
|
||||
|
||||
func UploadText(h *hyphae.Hypha, data []byte, message string, u *user.User) (hop *history.HistoryOp, errtitle string) {
|
||||
// UploadText edits a hypha' text part and makes a history record about that.
|
||||
func UploadText(h *hyphae.Hypha, data []byte, message string, u *user.User) (hop *history.Op, errtitle string) {
|
||||
hop = history.Operation(history.TypeEditText)
|
||||
var action string
|
||||
if h.Exists {
|
||||
@ -33,7 +34,7 @@ func UploadText(h *hyphae.Hypha, data []byte, message string, u *user.User) (hop
|
||||
hop.WithMsg(fmt.Sprintf("%s ‘%s’: %s", action, h.Name, message))
|
||||
}
|
||||
|
||||
if err, errtitle := CanEdit(u, h); err != nil {
|
||||
if errtitle, err := CanEdit(u, h); err != nil {
|
||||
return hop.WithErrAbort(err), errtitle
|
||||
}
|
||||
if len(bytes.TrimSpace(data)) == 0 && h.BinaryPath == "" {
|
||||
@ -43,7 +44,8 @@ func UploadText(h *hyphae.Hypha, data []byte, message string, u *user.User) (hop
|
||||
return uploadHelp(h, hop, ".myco", data, u)
|
||||
}
|
||||
|
||||
func UploadBinary(h *hyphae.Hypha, mime string, file multipart.File, u *user.User) (*history.HistoryOp, string) {
|
||||
// UploadBinary edits a hypha' attachment and makes a history record about that.
|
||||
func UploadBinary(h *hyphae.Hypha, mime string, file multipart.File, u *user.User) (*history.Op, string) {
|
||||
var (
|
||||
hop = history.Operation(history.TypeEditBinary).WithMsg(fmt.Sprintf("Upload attachment for ‘%s’ with type ‘%s’", h.Name, mime))
|
||||
data, err = io.ReadAll(file)
|
||||
@ -52,7 +54,7 @@ func UploadBinary(h *hyphae.Hypha, mime string, file multipart.File, u *user.Use
|
||||
if err != nil {
|
||||
return hop.WithErrAbort(err), err.Error()
|
||||
}
|
||||
if err, errtitle := CanAttach(u, h); err != nil {
|
||||
if errtitle, err := CanAttach(u, h); err != nil {
|
||||
return hop.WithErrAbort(err), errtitle
|
||||
}
|
||||
if len(data) == 0 {
|
||||
@ -63,7 +65,7 @@ func UploadBinary(h *hyphae.Hypha, mime string, file multipart.File, u *user.Use
|
||||
}
|
||||
|
||||
// uploadHelp is a helper function for UploadText and UploadBinary
|
||||
func uploadHelp(h *hyphae.Hypha, hop *history.HistoryOp, ext string, data []byte, u *user.User) (*history.HistoryOp, string) {
|
||||
func uploadHelp(h *hyphae.Hypha, hop *history.Op, ext string, data []byte, u *user.User) (*history.Op, string) {
|
||||
var (
|
||||
fullPath = filepath.Join(files.HyphaeDir(), h.Name+ext)
|
||||
originalFullPath = &h.TextPath
|
||||
|
@ -21,6 +21,7 @@ func FetchTextPart(h *hyphae.Hypha) (string, error) {
|
||||
return string(text), nil
|
||||
}
|
||||
|
||||
// SetHeaderLinks initializes header links by reading the configured hypha, if there is any, or resorting to default values.
|
||||
func SetHeaderLinks() {
|
||||
if userLinksHypha := hyphae.ByName(cfg.HeaderLinksHypha); !userLinksHypha.Exists {
|
||||
cfg.SetDefaultHeaderLinks()
|
||||
|
@ -137,14 +137,14 @@ func figureOutChildren(hyphaName string, subhyphaePool map[string]bool, exists b
|
||||
nestLevel = strings.Count(hyphaName, "/")
|
||||
adopted = make([]child, 0)
|
||||
)
|
||||
for subhyphaName, _ := range subhyphaePool {
|
||||
for subhyphaName := range subhyphaePool {
|
||||
subnestLevel := strings.Count(subhyphaName, "/")
|
||||
if subnestLevel-1 == nestLevel && path.Dir(subhyphaName) == hyphaName {
|
||||
delete(subhyphaePool, subhyphaName)
|
||||
adopted = append(adopted, figureOutChildren(subhyphaName, subhyphaePool, true))
|
||||
}
|
||||
}
|
||||
for descName, _ := range subhyphaePool {
|
||||
for descName := range subhyphaePool {
|
||||
if strings.HasPrefix(descName, hyphaName) {
|
||||
var (
|
||||
rawSubPath = strings.TrimPrefix(descName, hyphaName)[1:]
|
||||
|
@ -76,6 +76,7 @@ func readTokensToUsers() {
|
||||
log.Println("Found", len(tmp), "active sessions")
|
||||
}
|
||||
|
||||
// SaveUserDatabase stores current user credentials into JSON file by configured path.
|
||||
func SaveUserDatabase() error {
|
||||
return dumpUserCredentials()
|
||||
}
|
||||
|
@ -27,7 +27,7 @@ func FromRequest(rq *http.Request) *User {
|
||||
if err != nil {
|
||||
return EmptyUser()
|
||||
}
|
||||
return UserByToken(cookie.Value)
|
||||
return ByToken(cookie.Value)
|
||||
}
|
||||
|
||||
// LogoutFromRequest logs the user in `rq` out and rewrites the cookie in `w`.
|
||||
@ -108,9 +108,9 @@ func AddSession(username string) (string, error) {
|
||||
}
|
||||
|
||||
// A handy cookie constructor
|
||||
func cookie(name_suffix, val string, t time.Time) *http.Cookie {
|
||||
func cookie(nameSuffix, val string, t time.Time) *http.Cookie {
|
||||
return &http.Cookie{
|
||||
Name: "mycorrhiza_" + name_suffix,
|
||||
Name: "mycorrhiza_" + nameSuffix,
|
||||
Value: val,
|
||||
Expires: t,
|
||||
Path: "/",
|
||||
|
@ -9,7 +9,7 @@ import (
|
||||
"golang.org/x/crypto/bcrypt"
|
||||
)
|
||||
|
||||
// User is a user.
|
||||
// User is a user (duh).
|
||||
type User struct {
|
||||
// Name is a username. It must follow hypha naming rules.
|
||||
Name string `json:"name"`
|
||||
@ -59,6 +59,7 @@ var groupRight = map[string]int{
|
||||
"admin": 4,
|
||||
}
|
||||
|
||||
// ValidGroup checks whether provided user group name exists.
|
||||
func ValidGroup(group string) bool {
|
||||
for _, grp := range groups {
|
||||
if grp == group {
|
||||
@ -68,10 +69,12 @@ func ValidGroup(group string) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
// ValidSource checks whether provided user source name exists.
|
||||
func ValidSource(source string) bool {
|
||||
return source == "local" || source == "telegram"
|
||||
}
|
||||
|
||||
// EmptyUser constructs an anonymous user.
|
||||
func EmptyUser() *User {
|
||||
return &User{
|
||||
Name: "anon",
|
||||
@ -81,6 +84,7 @@ func EmptyUser() *User {
|
||||
}
|
||||
}
|
||||
|
||||
// CanProceed checks whether user has rights to visit the provided path (and perform an action).
|
||||
func (user *User) CanProceed(route string) bool {
|
||||
if !cfg.UseAuth {
|
||||
return true
|
||||
|
@ -5,6 +5,7 @@ import "sync"
|
||||
var users sync.Map
|
||||
var tokens sync.Map
|
||||
|
||||
// YieldUsers creates a channel which iterates existing users.
|
||||
func YieldUsers() chan *User {
|
||||
ch := make(chan *User)
|
||||
go func(ch chan *User) {
|
||||
@ -17,6 +18,7 @@ func YieldUsers() chan *User {
|
||||
return ch
|
||||
}
|
||||
|
||||
// ListUsersWithGroup returns a slice with users of desired group.
|
||||
func ListUsersWithGroup(group string) []string {
|
||||
filtered := []string{}
|
||||
for u := range YieldUsers() {
|
||||
@ -27,6 +29,7 @@ func ListUsersWithGroup(group string) []string {
|
||||
return filtered
|
||||
}
|
||||
|
||||
// Count returns total users count
|
||||
func Count() (i uint64) {
|
||||
users.Range(func(k, v interface{}) bool {
|
||||
i++
|
||||
@ -35,24 +38,29 @@ func Count() (i uint64) {
|
||||
return i
|
||||
}
|
||||
|
||||
// HasUsername checks whether the desired user exists
|
||||
func HasUsername(username string) bool {
|
||||
_, has := users.Load(username)
|
||||
return has
|
||||
}
|
||||
|
||||
// CredentialsOK checks whether a correct user-password pair is provided
|
||||
func CredentialsOK(username, password string) bool {
|
||||
return UserByName(username).isCorrectPassword(password)
|
||||
return ByName(username).isCorrectPassword(password)
|
||||
}
|
||||
|
||||
func UserByToken(token string) *User {
|
||||
// ByToken finds a user by provided session token
|
||||
func ByToken(token string) *User {
|
||||
// TODO: Needs more session data -- chekoopa
|
||||
if usernameUntyped, ok := tokens.Load(token); ok {
|
||||
username := usernameUntyped.(string)
|
||||
return UserByName(username)
|
||||
return ByName(username)
|
||||
}
|
||||
return EmptyUser()
|
||||
}
|
||||
|
||||
func UserByName(username string) *User {
|
||||
// ByName finds a user by one's username
|
||||
func ByName(username string) *User {
|
||||
if userUntyped, ok := users.Load(username); ok {
|
||||
user := userUntyped.(*User)
|
||||
return user
|
||||
@ -60,6 +68,7 @@ func UserByName(username string) *User {
|
||||
return EmptyUser()
|
||||
}
|
||||
|
||||
// DeleteUser removes a user by one's name and saves user database.
|
||||
func DeleteUser(name string) error {
|
||||
user, loaded := users.LoadAndDelete(name)
|
||||
if loaded {
|
||||
|
@ -111,6 +111,7 @@ type FormData struct {
|
||||
fields map[string]string
|
||||
}
|
||||
|
||||
// NewFormData constructs empty form data instance.
|
||||
func NewFormData() FormData {
|
||||
return FormData{
|
||||
err: nil,
|
||||
@ -118,6 +119,7 @@ func NewFormData() FormData {
|
||||
}
|
||||
}
|
||||
|
||||
// FormDataFromRequest extracts a form data from request, using a set of keys.
|
||||
func FormDataFromRequest(r *http.Request, keys []string) FormData {
|
||||
formData := NewFormData()
|
||||
for _, key := range keys {
|
||||
@ -126,10 +128,12 @@ func FormDataFromRequest(r *http.Request, keys []string) FormData {
|
||||
return formData
|
||||
}
|
||||
|
||||
// HasError is true if there is indeed an error.
|
||||
func (f FormData) HasError() bool {
|
||||
return f.err != nil
|
||||
}
|
||||
|
||||
// Error returns an error text or empty string, if there are no errors in form data.
|
||||
func (f FormData) Error() string {
|
||||
if f.err == nil {
|
||||
return ""
|
||||
@ -137,15 +141,18 @@ func (f FormData) Error() string {
|
||||
return f.err.Error()
|
||||
}
|
||||
|
||||
// WithError puts an error into form data and returns itself.
|
||||
func (f FormData) WithError(err error) FormData {
|
||||
f.err = err
|
||||
return f
|
||||
}
|
||||
|
||||
// Get accesses form data with a key
|
||||
func (f FormData) Get(key string) string {
|
||||
return f.fields[key]
|
||||
}
|
||||
|
||||
// Put writes a form value for provided key
|
||||
func (f FormData) Put(key, value string) {
|
||||
f.fields[key] = value
|
||||
}
|
||||
|
@ -79,7 +79,7 @@ func handlerAdminUsers(w http.ResponseWriter, rq *http.Request) {
|
||||
|
||||
func handlerAdminUserEdit(w http.ResponseWriter, rq *http.Request) {
|
||||
vars := mux.Vars(rq)
|
||||
u := user.UserByName(vars["username"])
|
||||
u := user.ByName(vars["username"])
|
||||
if u == nil {
|
||||
util.HTTP404Page(w, "404 page not found")
|
||||
return
|
||||
@ -121,7 +121,7 @@ func handlerAdminUserEdit(w http.ResponseWriter, rq *http.Request) {
|
||||
|
||||
func handlerAdminUserDelete(w http.ResponseWriter, rq *http.Request) {
|
||||
vars := mux.Vars(rq)
|
||||
u := user.UserByName(vars["username"])
|
||||
u := user.ByName(vars["username"])
|
||||
if u == nil {
|
||||
util.HTTP404Page(w, "404 page not found")
|
||||
return
|
||||
|
@ -143,7 +143,7 @@ func handlerTelegramLogin(w http.ResponseWriter, rq *http.Request) {
|
||||
false,
|
||||
)
|
||||
)
|
||||
if user.HasUsername(username) && user.UserByName(username).Source == "telegram" {
|
||||
if user.HasUsername(username) && user.ByName(username).Source == "telegram" {
|
||||
// Problems is something we put blankets on.
|
||||
err = nil
|
||||
}
|
||||
|
@ -36,7 +36,7 @@ func handlerHistory(w http.ResponseWriter, rq *http.Request) {
|
||||
// History can be found for files that do not exist anymore.
|
||||
revs, err := history.Revisions(hyphaName)
|
||||
if err == nil {
|
||||
list = history.HistoryWithRevisions(hyphaName, revs)
|
||||
list = history.WithRevisions(hyphaName, revs)
|
||||
}
|
||||
log.Println("Found", len(revs), "revisions for", hyphaName)
|
||||
|
||||
|
@ -35,7 +35,7 @@ func initMutators(r *mux.Router) {
|
||||
|
||||
func factoryHandlerAsker(
|
||||
actionPath string,
|
||||
asker func(*user.User, *hyphae.Hypha) (error, string),
|
||||
asker func(*user.User, *hyphae.Hypha) (string, error),
|
||||
succTitleKey string,
|
||||
succPageTemplate func(*http.Request, string, bool) string,
|
||||
) func(http.ResponseWriter, *http.Request) {
|
||||
@ -47,7 +47,7 @@ func factoryHandlerAsker(
|
||||
u = user.FromRequest(rq)
|
||||
lc = l18n.FromRequest(rq)
|
||||
)
|
||||
if err, errtitle := asker(u, h); err != nil {
|
||||
if errtitle, err := asker(u, h); err != nil {
|
||||
httpErr(
|
||||
w,
|
||||
lc,
|
||||
@ -90,7 +90,7 @@ var handlerRenameAsk = factoryHandlerAsker(
|
||||
|
||||
func factoryHandlerConfirmer(
|
||||
actionPath string,
|
||||
confirmer func(*hyphae.Hypha, *user.User, *http.Request) (*history.HistoryOp, string),
|
||||
confirmer func(*hyphae.Hypha, *user.User, *http.Request) (*history.Op, string),
|
||||
) func(http.ResponseWriter, *http.Request) {
|
||||
return func(w http.ResponseWriter, rq *http.Request) {
|
||||
util.PrepareRq(rq)
|
||||
@ -112,14 +112,14 @@ func factoryHandlerConfirmer(
|
||||
|
||||
var handlerUnattachConfirm = factoryHandlerConfirmer(
|
||||
"unattach-confirm",
|
||||
func(h *hyphae.Hypha, u *user.User, _ *http.Request) (*history.HistoryOp, string) {
|
||||
func(h *hyphae.Hypha, u *user.User, _ *http.Request) (*history.Op, string) {
|
||||
return shroom.UnattachHypha(u, h)
|
||||
},
|
||||
)
|
||||
|
||||
var handlerDeleteConfirm = factoryHandlerConfirmer(
|
||||
"delete-confirm",
|
||||
func(h *hyphae.Hypha, u *user.User, _ *http.Request) (*history.HistoryOp, string) {
|
||||
func(h *hyphae.Hypha, u *user.User, _ *http.Request) (*history.Op, string) {
|
||||
return shroom.DeleteHypha(u, h)
|
||||
},
|
||||
)
|
||||
@ -158,7 +158,7 @@ func handlerEdit(w http.ResponseWriter, rq *http.Request) {
|
||||
u = user.FromRequest(rq)
|
||||
lc = l18n.FromRequest(rq)
|
||||
)
|
||||
if err, errtitle := shroom.CanEdit(u, h); err != nil {
|
||||
if errtitle, err := shroom.CanEdit(u, h); err != nil {
|
||||
httpErr(w, lc, http.StatusInternalServerError, hyphaName,
|
||||
errtitle,
|
||||
err.Error())
|
||||
@ -196,7 +196,7 @@ func handlerUploadText(w http.ResponseWriter, rq *http.Request) {
|
||||
message = rq.PostFormValue("message")
|
||||
u = user.FromRequest(rq)
|
||||
lc = l18n.FromRequest(rq)
|
||||
hop *history.HistoryOp
|
||||
hop *history.Op
|
||||
errtitle string
|
||||
)
|
||||
|
||||
@ -247,7 +247,7 @@ func handlerUploadBinary(w http.ResponseWriter, rq *http.Request) {
|
||||
lc.Get("ui.error"),
|
||||
err.Error())
|
||||
}
|
||||
if err, errtitle := shroom.CanAttach(u, h); err != nil {
|
||||
if errtitle, err := shroom.CanAttach(u, h); err != nil {
|
||||
httpErr(w, lc, http.StatusInternalServerError, hyphaName,
|
||||
errtitle,
|
||||
err.Error())
|
||||
|
@ -55,10 +55,10 @@ func handlerAttachment(w http.ResponseWriter, rq *http.Request) {
|
||||
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:])
|
||||
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)
|
||||
lc = l18n.FromRequest(rq)
|
||||
@ -77,10 +77,10 @@ func handlerPrimitiveDiff(w http.ResponseWriter, rq *http.Request) {
|
||||
func handlerRevisionText(w http.ResponseWriter, rq *http.Request) {
|
||||
util.PrepareRq(rq)
|
||||
var (
|
||||
shorterUrl = strings.TrimPrefix(rq.URL.Path, "/rev-text/")
|
||||
firstSlashIndex = strings.IndexRune(shorterUrl, '/')
|
||||
revHash = shorterUrl[:firstSlashIndex]
|
||||
hyphaName = util.CanonicalName(shorterUrl[firstSlashIndex+1:])
|
||||
shorterURL = strings.TrimPrefix(rq.URL.Path, "/rev-text/")
|
||||
firstSlashIndex = strings.IndexRune(shorterURL, '/')
|
||||
revHash = shorterURL[:firstSlashIndex]
|
||||
hyphaName = util.CanonicalName(shorterURL[firstSlashIndex+1:])
|
||||
h = hyphae.ByName(hyphaName)
|
||||
textContents, err = history.FileAtRevision(h.TextPartPath(), revHash)
|
||||
)
|
||||
@ -101,10 +101,10 @@ func handlerRevision(w http.ResponseWriter, rq *http.Request) {
|
||||
util.PrepareRq(rq)
|
||||
var (
|
||||
lc = l18n.FromRequest(rq)
|
||||
shorterUrl = strings.TrimPrefix(rq.URL.Path, "/rev/")
|
||||
firstSlashIndex = strings.IndexRune(shorterUrl, '/')
|
||||
revHash = shorterUrl[:firstSlashIndex]
|
||||
hyphaName = util.CanonicalName(shorterUrl[firstSlashIndex+1:])
|
||||
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>%s</p>`, lc.Get("ui.revision_no_text"))
|
||||
textContents, err = history.FileAtRevision(h.TextPartPath(), revHash)
|
||||
|
@ -74,6 +74,7 @@ func handlerRobotsTxt(w http.ResponseWriter, rq *http.Request) {
|
||||
file.Close()
|
||||
}
|
||||
|
||||
// Handler initializes
|
||||
func Handler() http.Handler {
|
||||
router := mux.NewRouter()
|
||||
router.Use(func(next http.Handler) http.Handler {
|
||||
|
Loading…
Reference in New Issue
Block a user