1
0
mirror of https://github.com/osmarks/mycorrhiza.git synced 2025-01-19 07:02:51 +00:00

Merge pull request #100 from chekoopa/tidy-up

Housekeeping with linter
This commit is contained in:
Timur Ismagilov 2021-10-01 20:19:58 +03:00 committed by GitHub
commit 54fdb29ddf
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
29 changed files with 139 additions and 85 deletions

View File

@ -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"> <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. **Mycorrhiza Wiki** is a lightweight file-system wiki engine that uses Git for keeping history.
[👉 Main wiki](https://mycorrhiza.wiki) [👉 Main wiki](https://mycorrhiza.wiki)

View File

@ -28,6 +28,7 @@ func printHelp() {
flag.PrintDefaults() flag.PrintDefaults()
} }
// CreateUserCommand is parameters for admin creation flag. Only a single parameter, though.
type CreateUserCommand struct { type CreateUserCommand struct {
name string name string
} }

View File

@ -33,6 +33,7 @@ func Start() {
gitpath = path gitpath = path
} }
// InitGitRepo checks a Git repository and initializes it if necessary.
func InitGitRepo() { func InitGitRepo() {
// Detect if the Git repo directory is a Git repository // Detect if the Git repo directory is a Git repository
isGitRepo := true isGitRepo := true
@ -144,7 +145,7 @@ func (rev *Revision) textDiff() (diff string) {
for _, filename := range filenames { for _, filename := range filenames {
text, err := PrimitiveDiffAtRevision(filename, rev.Hash) text, err := PrimitiveDiffAtRevision(filename, rev.Hash)
if err != nil { if err != nil {
diff += "\nAn error has occured with " + filename + "\n" diff += "\nAn error has occurred with " + filename + "\n"
} }
diff += text + "\n" diff += text + "\n"
} }

View File

@ -52,18 +52,22 @@ func recentChangesFeed() *feeds.Feed {
return feed return feed
} }
// RecentChangesRSS creates recent changes feed in RSS format.
func RecentChangesRSS() (string, error) { func RecentChangesRSS() (string, error) {
return recentChangesFeed().ToRss() return recentChangesFeed().ToRss()
} }
// RecentChangesAtom creates recent changes feed in Atom format.
func RecentChangesAtom() (string, error) { func RecentChangesAtom() (string, error) {
return recentChangesFeed().ToAtom() return recentChangesFeed().ToAtom()
} }
// RecentChangesJSON creates recent changes feed in JSON format.
func RecentChangesJSON() (string, error) { func RecentChangesJSON() (string, error) {
return recentChangesFeed().ToJSON() return recentChangesFeed().ToJSON()
} }
// RecentChanges gathers an arbitrary number of latest changes in form of revisions slice.
func RecentChanges(n int) []Revision { func RecentChanges(n int) []Revision {
var ( var (
out, err = silentGitsh( out, err = silentGitsh(
@ -110,8 +114,8 @@ func Revisions(hyphaName string) ([]Revision, error) {
return revs, err return revs, err
} }
// HistoryWithRevisions returns an html representation of `revs` that is meant to be inserted in a history page. // WithRevisions returns an html representation of `revs` that is meant to be inserted in a history page.
func HistoryWithRevisions(hyphaName string, revs []Revision) (html string) { func WithRevisions(hyphaName string, revs []Revision) (html string) {
var ( var (
currentYear int currentYear int
currentMonth time.Month currentMonth time.Month

View File

@ -19,16 +19,22 @@ var gitMutex = sync.Mutex{}
type OpType int type OpType int
const ( const (
// TypeNone represents an empty operation. Not to be used in practice.
TypeNone OpType = iota TypeNone OpType = iota
// TypeEditText represents an edit of hypha text part.
TypeEditText TypeEditText
// TypeEditBinary represents an addition or replacement of hypha attachment.
TypeEditBinary TypeEditBinary
// TypeDeleteHypha represents a hypha deletion
TypeDeleteHypha TypeDeleteHypha
// TypeRenameHypha represents a hypha renaming
TypeRenameHypha TypeRenameHypha
// TypeUnattachHypha represents a hypha attachment deletion
TypeUnattachHypha TypeUnattachHypha
) )
// HistoryOp is an object representing a history operation. // Op is an object representing a history operation.
type HistoryOp struct { type Op struct {
// All errors are appended here. // All errors are appended here.
Errs []error Errs []error
Type OpType Type OpType
@ -38,9 +44,9 @@ type HistoryOp struct {
} }
// Operation is a constructor of a history operation. // Operation is a constructor of a history operation.
func Operation(opType OpType) *HistoryOp { func Operation(opType OpType) *Op {
gitMutex.Lock() gitMutex.Lock()
hop := &HistoryOp{ hop := &Op{
Errs: []error{}, Errs: []error{},
name: "anon", name: "anon",
email: "anon@mycorrhiza", email: "anon@mycorrhiza",
@ -50,7 +56,7 @@ func Operation(opType OpType) *HistoryOp {
} }
// git operation maker helper // git operation maker helper
func (hop *HistoryOp) gitop(args ...string) *HistoryOp { func (hop *Op) gitop(args ...string) *Op {
out, err := gitsh(args...) out, err := gitsh(args...)
if err != nil { if err != nil {
fmt.Println("out:", out.String()) 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. // 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) hop.Errs = append(hop.Errs, err)
return hop 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() 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. // 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", "--"} args := []string{"rm", "--quiet", "--"}
for _, path := range paths { for _, path := range paths {
if path != "" { 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. // 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 { for from, to := range pairs {
if from != "" { if from != "" {
if err := os.MkdirAll(filepath.Dir(to), 0777); err != nil { 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. // 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 { for i, path := range paths {
paths[i] = util.ShorterPath(path) paths[i] = util.ShorterPath(path)
} }
@ -106,7 +113,7 @@ func (hop *HistoryOp) WithFiles(paths ...string) *HistoryOp {
} }
// Apply applies history operation by doing the commit. // Apply applies history operation by doing the commit.
func (hop *HistoryOp) Apply() *HistoryOp { func (hop *Op) Apply() *Op {
hop.gitop( hop.gitop(
"commit", "commit",
"--author='"+hop.name+" <"+hop.email+">'", "--author='"+hop.name+" <"+hop.email+">'",
@ -117,13 +124,13 @@ func (hop *HistoryOp) Apply() *HistoryOp {
} }
// Abort aborts the history operation. // Abort aborts the history operation.
func (hop *HistoryOp) Abort() *HistoryOp { func (hop *Op) Abort() *Op {
gitMutex.Unlock() gitMutex.Unlock()
return hop return hop
} }
// WithMsg sets what message will be used for the future commit. If user message exceeds one line, it is stripped down. // 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 { for _, ch := range userMsg {
if ch == '\r' || ch == '\n' { if ch == '\r' || ch == '\n' {
break break
@ -134,7 +141,7 @@ func (hop *HistoryOp) WithMsg(userMsg string) *HistoryOp {
} }
// WithUser sets a user for the commit. // 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" { if u.Group != "anon" {
hop.name = u.Name hop.name = u.Name
hop.email = u.Name + "@mycorrhiza" hop.email = u.Name + "@mycorrhiza"
@ -142,10 +149,12 @@ func (hop *HistoryOp) WithUser(u *user.User) *HistoryOp {
return hop return hop
} }
func (hop *HistoryOp) HasErrors() bool { // HasErrors checks whether operation has errors appended.
func (hop *Op) HasErrors() bool {
return len(hop.Errs) > 0 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() return hop.Errs[0].Error()
} }

View File

@ -38,12 +38,14 @@ type BacklinkIndexOperation interface {
Apply() Apply()
} }
// BacklinkIndexEdit contains data for backlink index update after a hypha edit
type BacklinkIndexEdit struct { type BacklinkIndexEdit struct {
Name string Name string
OldLinks []string OldLinks []string
NewLinks []string NewLinks []string
} }
// Apply changes backlink index respective to the operation data
func (op BacklinkIndexEdit) Apply() { func (op BacklinkIndexEdit) Apply() {
oldLinks := toLinkSet(op.OldLinks) oldLinks := toLinkSet(op.OldLinks)
newLinks := toLinkSet(op.NewLinks) 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 { type BacklinkIndexDeletion struct {
Name string Name string
Links []string Links []string
} }
// Apply changes backlink index respective to the operation data
func (op BacklinkIndexDeletion) Apply() { func (op BacklinkIndexDeletion) Apply() {
for _, link := range op.Links { for _, link := range op.Links {
if lSet, exists := backlinkIndex[link]; exists { 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 { type BacklinkIndexRenaming struct {
OldName string OldName string
NewName string NewName string
Links []string Links []string
} }
// Apply changes backlink index respective to the operation data
func (op BacklinkIndexRenaming) Apply() { func (op BacklinkIndexRenaming) Apply() {
for _, link := range op.Links { for _, link := range op.Links {
if lSet, exists := backlinkIndex[link]; exists { if lSet, exists := backlinkIndex[link]; exists {

View File

@ -10,21 +10,21 @@ var count = struct {
sync.Mutex 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() { func ResetCount() {
count.Lock() count.Lock()
count.value = 0 count.value = 0
count.Unlock() 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() { func IncrementCount() {
count.Lock() count.Lock()
count.value++ count.value++
count.Unlock() 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() { func DecrementCount() {
count.Lock() count.Lock()
count.value-- count.value--

View File

@ -12,6 +12,7 @@ import (
// HyphaPattern is a pattern which all hyphae must match. // HyphaPattern is a pattern which all hyphae must match.
var HyphaPattern = regexp.MustCompile(`[^?!:#@><*|"'&%{}]+`) var HyphaPattern = regexp.MustCompile(`[^?!:#@><*|"'&%{}]+`)
// Hypha keeps vital information about a hypha
type Hypha struct { type Hypha struct {
sync.RWMutex sync.RWMutex
@ -79,6 +80,7 @@ func (h *Hypha) Insert() (justRecorded bool) {
return !recorded return !recorded
} }
// InsertIfNew checks whether hypha exists and returns `true` if it didn't and has been created.
func (h *Hypha) InsertIfNew() (justRecorded bool) { func (h *Hypha) InsertIfNew() (justRecorded bool) {
if !h.Exists { if !h.Exists {
return h.Insert() return h.Insert()
@ -86,6 +88,7 @@ func (h *Hypha) InsertIfNew() (justRecorded bool) {
return false return false
} }
// Delete removes a hypha from the storage.
func (h *Hypha) Delete() { func (h *Hypha) Delete() {
byNamesMutex.Lock() byNamesMutex.Lock()
h.Lock() h.Lock()
@ -95,6 +98,7 @@ func (h *Hypha) Delete() {
h.Unlock() h.Unlock()
} }
// RenameTo renames a hypha and performs respective changes in the storage.
func (h *Hypha) RenameTo(newName string) { func (h *Hypha) RenameTo(newName string) {
byNamesMutex.Lock() byNamesMutex.Lock()
h.Lock() h.Lock()

View File

@ -11,6 +11,7 @@ type Iteration struct {
checks []func(h *Hypha) CheckResult checks []func(h *Hypha) CheckResult
} }
// NewIteration constructs an iteration without checks.
func NewIteration() *Iteration { func NewIteration() *Iteration {
return &Iteration{ return &Iteration{
iterator: YieldExistingHyphae, iterator: YieldExistingHyphae,

View File

@ -1,6 +1,7 @@
// File `iterators.go` contains stuff that iterates over hyphae.
package hyphae package hyphae
// File `iterators.go` contains stuff that iterates over hyphae.
import ( import (
"sort" "sort"
"strings" "strings"

View File

@ -14,7 +14,7 @@ var locales = language.NewMatcher([]language.Tag{
language.Make("ru"), 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 { func FromRequest(r *http.Request) *Localizer {
t, _, _ := language.ParseAcceptLanguage(r.Header.Get("Accept-Language")) t, _, _ := language.ParseAcceptLanguage(r.Header.Get("Accept-Language"))
tag, _, _ := locales.Match(t...) tag, _, _ := locales.Match(t...)
@ -91,7 +91,7 @@ func getLocalizationKey(locale string, key string) string {
/* chekoopa: Missing translation features: /* chekoopa: Missing translation features:
- history records (they use Git description, the possible solution is to parse and translate) - 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) - 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) - 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 - alt solution is implementing "special" links

View File

@ -14,29 +14,30 @@ func canFactory(
noRightsMsg string, noRightsMsg string,
notExistsMsg string, notExistsMsg string,
careAboutExistence bool, careAboutExistence bool,
) func(*user.User, *hyphae.Hypha) (error, string) { ) func(*user.User, *hyphae.Hypha) (string, error) {
return func(u *user.User, h *hyphae.Hypha) (error, string) { return func(u *user.User, h *hyphae.Hypha) (string, error) {
if !u.CanProceed(action) { if !u.CanProceed(action) {
rejectLogger(h, u, "no rights") rejectLogger(h, u, "no rights")
return errors.New(noRightsMsg), "Not enough rights" return "Not enough rights", errors.New(noRightsMsg)
} }
if careAboutExistence && !h.Exists { if careAboutExistence && !h.Exists {
rejectLogger(h, u, "does not exist") rejectLogger(h, u, "does not exist")
return errors.New(notExistsMsg), "Does not exist" return "Does not exist", errors.New(notExistsMsg)
} }
if dispatcher == nil { if dispatcher == nil {
return nil, "" return "", nil
} }
errmsg, errtitle := dispatcher(h, u) errmsg, errtitle := dispatcher(h, u)
if errtitle == "" { 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 ( var (
CanDelete = canFactory( CanDelete = canFactory(
rejectDeleteLog, rejectDeleteLog,

View File

@ -9,10 +9,10 @@ import (
) )
// DeleteHypha deletes hypha and makes a history record about that. // 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) hop = history.Operation(history.TypeDeleteHypha)
if err, errtitle := CanDelete(u, h); errtitle != "" { if errtitle, err := CanDelete(u, h); errtitle != "" {
hop.WithErrAbort(err) hop.WithErrAbort(err)
return hop, errtitle return hop, errtitle
} }

View File

@ -11,36 +11,36 @@ import (
"github.com/bouncepaw/mycorrhiza/util" "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 { if nh.Exists {
rejectRenameLog(oh, u, fmt.Sprintf("name %s taken already", nh.Name)) 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 == "" { if nh.Name == "" {
rejectRenameLog(oh, u, "no new name given") 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) { if !hyphae.HyphaPattern.MatchString(nh.Name) {
rejectRenameLog(oh, u, fmt.Sprintf("new name %s invalid", nh.Name)) rejectRenameLog(oh, u, fmt.Sprintf("new name %s invalid", nh.Name))
return errors.New("Invalid new name. Names cannot contain characters <code>^?!:#@&gt;&lt;*|\"\\'&amp;%</code>"), "Invalid name" return "Invalid name", errors.New("Invalid new name. Names cannot contain characters <code>^?!:#@&gt;&lt;*|\"\\'&amp;%</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. // 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() newHypha.Lock()
defer newHypha.Unlock() defer newHypha.Unlock()
hop = history.Operation(history.TypeRenameHypha) hop = history.Operation(history.TypeRenameHypha)
if err, errtitle := CanRename(u, h); errtitle != "" { if errtitle, err := CanRename(u, h); errtitle != "" {
hop.WithErrAbort(err) hop.WithErrAbort(err)
return hop, errtitle return hop, errtitle
} }
if err, errtitle := canRenameThisToThat(h, newHypha, u); errtitle != "" { if errtitle, err := canRenameThisToThat(h, newHypha, u); errtitle != "" {
hop.WithErrAbort(err) hop.WithErrAbort(err)
return hop, errtitle return hop, errtitle
} }

View File

@ -1,7 +1,6 @@
package shroom package shroom
import ( import (
"errors"
"fmt" "fmt"
"github.com/bouncepaw/mycorrhiza/history" "github.com/bouncepaw/mycorrhiza/history"
@ -10,10 +9,10 @@ import (
) )
// UnattachHypha unattaches hypha and makes a history record about that. // 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) hop = history.Operation(history.TypeUnattachHypha)
if err, errtitle := CanUnattach(u, h); errtitle != "" { if errtitle, err := CanUnattach(u, h); errtitle != "" {
hop.WithErrAbort(err) hop.WithErrAbort(err)
return hop, errtitle return hop, errtitle
} }
@ -27,7 +26,7 @@ func UnattachHypha(u *user.User, h *hyphae.Hypha) (hop *history.HistoryOp, errti
if len(hop.Errs) > 0 { if len(hop.Errs) > 0 {
rejectUnattachLog(h, u, "fail") rejectUnattachLog(h, u, "fail")
// FIXME: something may be wrong here // 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 != "" { if h.BinaryPath != "" {

View File

@ -18,7 +18,8 @@ import (
"github.com/bouncepaw/mycorrhiza/user" "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) hop = history.Operation(history.TypeEditText)
var action string var action string
if h.Exists { 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)) 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 return hop.WithErrAbort(err), errtitle
} }
if len(bytes.TrimSpace(data)) == 0 && h.BinaryPath == "" { 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) 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 ( var (
hop = history.Operation(history.TypeEditBinary).WithMsg(fmt.Sprintf("Upload attachment for %s with type %s", h.Name, mime)) hop = history.Operation(history.TypeEditBinary).WithMsg(fmt.Sprintf("Upload attachment for %s with type %s", h.Name, mime))
data, err = io.ReadAll(file) 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 { if err != nil {
return hop.WithErrAbort(err), err.Error() 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 return hop.WithErrAbort(err), errtitle
} }
if len(data) == 0 { 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 // 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 ( var (
fullPath = filepath.Join(files.HyphaeDir(), h.Name+ext) fullPath = filepath.Join(files.HyphaeDir(), h.Name+ext)
originalFullPath = &h.TextPath originalFullPath = &h.TextPath

View File

@ -21,6 +21,7 @@ func FetchTextPart(h *hyphae.Hypha) (string, error) {
return string(text), nil return string(text), nil
} }
// SetHeaderLinks initializes header links by reading the configured hypha, if there is any, or resorting to default values.
func SetHeaderLinks() { func SetHeaderLinks() {
if userLinksHypha := hyphae.ByName(cfg.HeaderLinksHypha); !userLinksHypha.Exists { if userLinksHypha := hyphae.ByName(cfg.HeaderLinksHypha); !userLinksHypha.Exists {
cfg.SetDefaultHeaderLinks() cfg.SetDefaultHeaderLinks()

View File

@ -137,14 +137,14 @@ func figureOutChildren(hyphaName string, subhyphaePool map[string]bool, exists b
nestLevel = strings.Count(hyphaName, "/") nestLevel = strings.Count(hyphaName, "/")
adopted = make([]child, 0) adopted = make([]child, 0)
) )
for subhyphaName, _ := range subhyphaePool { for subhyphaName := range subhyphaePool {
subnestLevel := strings.Count(subhyphaName, "/") subnestLevel := strings.Count(subhyphaName, "/")
if subnestLevel-1 == nestLevel && path.Dir(subhyphaName) == hyphaName { if subnestLevel-1 == nestLevel && path.Dir(subhyphaName) == hyphaName {
delete(subhyphaePool, subhyphaName) delete(subhyphaePool, subhyphaName)
adopted = append(adopted, figureOutChildren(subhyphaName, subhyphaePool, true)) adopted = append(adopted, figureOutChildren(subhyphaName, subhyphaePool, true))
} }
} }
for descName, _ := range subhyphaePool { for descName := range subhyphaePool {
if strings.HasPrefix(descName, hyphaName) { if strings.HasPrefix(descName, hyphaName) {
var ( var (
rawSubPath = strings.TrimPrefix(descName, hyphaName)[1:] rawSubPath = strings.TrimPrefix(descName, hyphaName)[1:]

View File

@ -76,6 +76,7 @@ func readTokensToUsers() {
log.Println("Found", len(tmp), "active sessions") log.Println("Found", len(tmp), "active sessions")
} }
// SaveUserDatabase stores current user credentials into JSON file by configured path.
func SaveUserDatabase() error { func SaveUserDatabase() error {
return dumpUserCredentials() return dumpUserCredentials()
} }

View File

@ -27,7 +27,7 @@ func FromRequest(rq *http.Request) *User {
if err != nil { if err != nil {
return EmptyUser() return EmptyUser()
} }
return UserByToken(cookie.Value) return ByToken(cookie.Value)
} }
// LogoutFromRequest logs the user in `rq` out and rewrites the cookie in `w`. // 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 // 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{ return &http.Cookie{
Name: "mycorrhiza_" + name_suffix, Name: "mycorrhiza_" + nameSuffix,
Value: val, Value: val,
Expires: t, Expires: t,
Path: "/", Path: "/",

View File

@ -9,7 +9,7 @@ import (
"golang.org/x/crypto/bcrypt" "golang.org/x/crypto/bcrypt"
) )
// User is a user. // User is a user (duh).
type User struct { type User struct {
// Name is a username. It must follow hypha naming rules. // Name is a username. It must follow hypha naming rules.
Name string `json:"name"` Name string `json:"name"`
@ -59,6 +59,7 @@ var groupRight = map[string]int{
"admin": 4, "admin": 4,
} }
// ValidGroup checks whether provided user group name exists.
func ValidGroup(group string) bool { func ValidGroup(group string) bool {
for _, grp := range groups { for _, grp := range groups {
if grp == group { if grp == group {
@ -68,10 +69,12 @@ func ValidGroup(group string) bool {
return false return false
} }
// ValidSource checks whether provided user source name exists.
func ValidSource(source string) bool { func ValidSource(source string) bool {
return source == "local" || source == "telegram" return source == "local" || source == "telegram"
} }
// EmptyUser constructs an anonymous user.
func EmptyUser() *User { func EmptyUser() *User {
return &User{ return &User{
Name: "anon", 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 { func (user *User) CanProceed(route string) bool {
if !cfg.UseAuth { if !cfg.UseAuth {
return true return true

View File

@ -5,6 +5,7 @@ import "sync"
var users sync.Map var users sync.Map
var tokens sync.Map var tokens sync.Map
// YieldUsers creates a channel which iterates existing users.
func YieldUsers() chan *User { func YieldUsers() chan *User {
ch := make(chan *User) ch := make(chan *User)
go func(ch chan *User) { go func(ch chan *User) {
@ -17,6 +18,7 @@ func YieldUsers() chan *User {
return ch return ch
} }
// ListUsersWithGroup returns a slice with users of desired group.
func ListUsersWithGroup(group string) []string { func ListUsersWithGroup(group string) []string {
filtered := []string{} filtered := []string{}
for u := range YieldUsers() { for u := range YieldUsers() {
@ -27,6 +29,7 @@ func ListUsersWithGroup(group string) []string {
return filtered return filtered
} }
// Count returns total users count
func Count() (i uint64) { func Count() (i uint64) {
users.Range(func(k, v interface{}) bool { users.Range(func(k, v interface{}) bool {
i++ i++
@ -35,24 +38,29 @@ func Count() (i uint64) {
return i return i
} }
// HasUsername checks whether the desired user exists
func HasUsername(username string) bool { func HasUsername(username string) bool {
_, has := users.Load(username) _, has := users.Load(username)
return has return has
} }
// CredentialsOK checks whether a correct user-password pair is provided
func CredentialsOK(username, password string) bool { 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 { if usernameUntyped, ok := tokens.Load(token); ok {
username := usernameUntyped.(string) username := usernameUntyped.(string)
return UserByName(username) return ByName(username)
} }
return EmptyUser() 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 { if userUntyped, ok := users.Load(username); ok {
user := userUntyped.(*User) user := userUntyped.(*User)
return user return user
@ -60,6 +68,7 @@ func UserByName(username string) *User {
return EmptyUser() return EmptyUser()
} }
// DeleteUser removes a user by one's name and saves user database.
func DeleteUser(name string) error { func DeleteUser(name string) error {
user, loaded := users.LoadAndDelete(name) user, loaded := users.LoadAndDelete(name)
if loaded { if loaded {

View File

@ -111,6 +111,7 @@ type FormData struct {
fields map[string]string fields map[string]string
} }
// NewFormData constructs empty form data instance.
func NewFormData() FormData { func NewFormData() FormData {
return FormData{ return FormData{
err: nil, 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 { func FormDataFromRequest(r *http.Request, keys []string) FormData {
formData := NewFormData() formData := NewFormData()
for _, key := range keys { for _, key := range keys {
@ -126,10 +128,12 @@ func FormDataFromRequest(r *http.Request, keys []string) FormData {
return formData return formData
} }
// HasError is true if there is indeed an error.
func (f FormData) HasError() bool { func (f FormData) HasError() bool {
return f.err != nil 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 { func (f FormData) Error() string {
if f.err == nil { if f.err == nil {
return "" return ""
@ -137,15 +141,18 @@ func (f FormData) Error() string {
return f.err.Error() return f.err.Error()
} }
// WithError puts an error into form data and returns itself.
func (f FormData) WithError(err error) FormData { func (f FormData) WithError(err error) FormData {
f.err = err f.err = err
return f return f
} }
// Get accesses form data with a key
func (f FormData) Get(key string) string { func (f FormData) Get(key string) string {
return f.fields[key] return f.fields[key]
} }
// Put writes a form value for provided key
func (f FormData) Put(key, value string) { func (f FormData) Put(key, value string) {
f.fields[key] = value f.fields[key] = value
} }

View File

@ -79,7 +79,7 @@ func handlerAdminUsers(w http.ResponseWriter, rq *http.Request) {
func handlerAdminUserEdit(w http.ResponseWriter, rq *http.Request) { func handlerAdminUserEdit(w http.ResponseWriter, rq *http.Request) {
vars := mux.Vars(rq) vars := mux.Vars(rq)
u := user.UserByName(vars["username"]) u := user.ByName(vars["username"])
if u == nil { if u == nil {
util.HTTP404Page(w, "404 page not found") util.HTTP404Page(w, "404 page not found")
return return
@ -121,7 +121,7 @@ func handlerAdminUserEdit(w http.ResponseWriter, rq *http.Request) {
func handlerAdminUserDelete(w http.ResponseWriter, rq *http.Request) { func handlerAdminUserDelete(w http.ResponseWriter, rq *http.Request) {
vars := mux.Vars(rq) vars := mux.Vars(rq)
u := user.UserByName(vars["username"]) u := user.ByName(vars["username"])
if u == nil { if u == nil {
util.HTTP404Page(w, "404 page not found") util.HTTP404Page(w, "404 page not found")
return return

View File

@ -143,7 +143,7 @@ func handlerTelegramLogin(w http.ResponseWriter, rq *http.Request) {
false, 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. // Problems is something we put blankets on.
err = nil err = nil
} }

View File

@ -36,7 +36,7 @@ func handlerHistory(w http.ResponseWriter, rq *http.Request) {
// History can be found for files that do not exist anymore. // History can be found for files that do not exist anymore.
revs, err := history.Revisions(hyphaName) revs, err := history.Revisions(hyphaName)
if err == nil { if err == nil {
list = history.HistoryWithRevisions(hyphaName, revs) list = history.WithRevisions(hyphaName, revs)
} }
log.Println("Found", len(revs), "revisions for", hyphaName) log.Println("Found", len(revs), "revisions for", hyphaName)

View File

@ -35,7 +35,7 @@ func initMutators(r *mux.Router) {
func factoryHandlerAsker( func factoryHandlerAsker(
actionPath string, actionPath string,
asker func(*user.User, *hyphae.Hypha) (error, string), asker func(*user.User, *hyphae.Hypha) (string, error),
succTitleKey string, succTitleKey string,
succPageTemplate func(*http.Request, string, bool) string, succPageTemplate func(*http.Request, string, bool) string,
) func(http.ResponseWriter, *http.Request) { ) func(http.ResponseWriter, *http.Request) {
@ -47,7 +47,7 @@ func factoryHandlerAsker(
u = user.FromRequest(rq) u = user.FromRequest(rq)
lc = l18n.FromRequest(rq) lc = l18n.FromRequest(rq)
) )
if err, errtitle := asker(u, h); err != nil { if errtitle, err := asker(u, h); err != nil {
httpErr( httpErr(
w, w,
lc, lc,
@ -90,7 +90,7 @@ var handlerRenameAsk = factoryHandlerAsker(
func factoryHandlerConfirmer( func factoryHandlerConfirmer(
actionPath string, 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) { ) func(http.ResponseWriter, *http.Request) {
return func(w http.ResponseWriter, rq *http.Request) { return func(w http.ResponseWriter, rq *http.Request) {
util.PrepareRq(rq) util.PrepareRq(rq)
@ -112,14 +112,14 @@ func factoryHandlerConfirmer(
var handlerUnattachConfirm = factoryHandlerConfirmer( var handlerUnattachConfirm = factoryHandlerConfirmer(
"unattach-confirm", "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) return shroom.UnattachHypha(u, h)
}, },
) )
var handlerDeleteConfirm = factoryHandlerConfirmer( var handlerDeleteConfirm = factoryHandlerConfirmer(
"delete-confirm", "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) return shroom.DeleteHypha(u, h)
}, },
) )
@ -158,7 +158,7 @@ func handlerEdit(w http.ResponseWriter, rq *http.Request) {
u = user.FromRequest(rq) u = user.FromRequest(rq)
lc = l18n.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, httpErr(w, lc, http.StatusInternalServerError, hyphaName,
errtitle, errtitle,
err.Error()) err.Error())
@ -196,7 +196,7 @@ func handlerUploadText(w http.ResponseWriter, rq *http.Request) {
message = rq.PostFormValue("message") message = rq.PostFormValue("message")
u = user.FromRequest(rq) u = user.FromRequest(rq)
lc = l18n.FromRequest(rq) lc = l18n.FromRequest(rq)
hop *history.HistoryOp hop *history.Op
errtitle string errtitle string
) )
@ -247,7 +247,7 @@ func handlerUploadBinary(w http.ResponseWriter, rq *http.Request) {
lc.Get("ui.error"), lc.Get("ui.error"),
err.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, httpErr(w, lc, http.StatusInternalServerError, hyphaName,
errtitle, errtitle,
err.Error()) err.Error())

View File

@ -55,10 +55,10 @@ func handlerAttachment(w http.ResponseWriter, rq *http.Request) {
func handlerPrimitiveDiff(w http.ResponseWriter, rq *http.Request) { func handlerPrimitiveDiff(w http.ResponseWriter, rq *http.Request) {
util.PrepareRq(rq) util.PrepareRq(rq)
var ( var (
shorterUrl = strings.TrimPrefix(rq.URL.Path, "/primitive-diff/") shorterURL = strings.TrimPrefix(rq.URL.Path, "/primitive-diff/")
firstSlashIndex = strings.IndexRune(shorterUrl, '/') firstSlashIndex = strings.IndexRune(shorterURL, '/')
revHash = shorterUrl[:firstSlashIndex] revHash = shorterURL[:firstSlashIndex]
hyphaName = util.CanonicalName(shorterUrl[firstSlashIndex+1:]) hyphaName = util.CanonicalName(shorterURL[firstSlashIndex+1:])
h = hyphae.ByName(hyphaName) h = hyphae.ByName(hyphaName)
u = user.FromRequest(rq) u = user.FromRequest(rq)
lc = l18n.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) { func handlerRevisionText(w http.ResponseWriter, rq *http.Request) {
util.PrepareRq(rq) util.PrepareRq(rq)
var ( var (
shorterUrl = strings.TrimPrefix(rq.URL.Path, "/rev-text/") shorterURL = strings.TrimPrefix(rq.URL.Path, "/rev-text/")
firstSlashIndex = strings.IndexRune(shorterUrl, '/') firstSlashIndex = strings.IndexRune(shorterURL, '/')
revHash = shorterUrl[:firstSlashIndex] revHash = shorterURL[:firstSlashIndex]
hyphaName = util.CanonicalName(shorterUrl[firstSlashIndex+1:]) hyphaName = util.CanonicalName(shorterURL[firstSlashIndex+1:])
h = hyphae.ByName(hyphaName) h = hyphae.ByName(hyphaName)
textContents, err = history.FileAtRevision(h.TextPartPath(), revHash) textContents, err = history.FileAtRevision(h.TextPartPath(), revHash)
) )
@ -101,10 +101,10 @@ func handlerRevision(w http.ResponseWriter, rq *http.Request) {
util.PrepareRq(rq) util.PrepareRq(rq)
var ( var (
lc = l18n.FromRequest(rq) lc = l18n.FromRequest(rq)
shorterUrl = strings.TrimPrefix(rq.URL.Path, "/rev/") shorterURL = strings.TrimPrefix(rq.URL.Path, "/rev/")
firstSlashIndex = strings.IndexRune(shorterUrl, '/') firstSlashIndex = strings.IndexRune(shorterURL, '/')
revHash = shorterUrl[:firstSlashIndex] revHash = shorterURL[:firstSlashIndex]
hyphaName = util.CanonicalName(shorterUrl[firstSlashIndex+1:]) hyphaName = util.CanonicalName(shorterURL[firstSlashIndex+1:])
h = hyphae.ByName(hyphaName) h = hyphae.ByName(hyphaName)
contents = fmt.Sprintf(`<p>%s</p>`, lc.Get("ui.revision_no_text")) contents = fmt.Sprintf(`<p>%s</p>`, lc.Get("ui.revision_no_text"))
textContents, err = history.FileAtRevision(h.TextPartPath(), revHash) textContents, err = history.FileAtRevision(h.TextPartPath(), revHash)

View File

@ -74,6 +74,7 @@ func handlerRobotsTxt(w http.ResponseWriter, rq *http.Request) {
file.Close() file.Close()
} }
// Handler initializes
func Handler() http.Handler { func Handler() http.Handler {
router := mux.NewRouter() router := mux.NewRouter()
router.Use(func(next http.Handler) http.Handler { router.Use(func(next http.Handler) http.Handler {