diff --git a/history/operations.go b/history/operations.go index 49195b6..e788b6e 100644 --- a/history/operations.go +++ b/history/operations.go @@ -24,6 +24,7 @@ const ( TypeEditBinary TypeDeleteHypha TypeRenameHypha + TypeUnattachHypha ) // HistoryOp is an object representing a history operation. diff --git a/http_mutators.go b/http_mutators.go index ff81346..61d1da9 100644 --- a/http_mutators.go +++ b/http_mutators.go @@ -16,11 +16,65 @@ func init() { http.HandleFunc("/edit/", handlerEdit) http.HandleFunc("/delete-ask/", handlerDeleteAsk) http.HandleFunc("/rename-ask/", handlerRenameAsk) + http.HandleFunc("/unattach-ask/", handlerUnattachAsk) // And those that do mutate something: http.HandleFunc("/upload-binary/", handlerUploadBinary) http.HandleFunc("/upload-text/", handlerUploadText) http.HandleFunc("/delete-confirm/", handlerDeleteConfirm) http.HandleFunc("/rename-confirm/", handlerRenameConfirm) + http.HandleFunc("/unattach-confirm/", handlerUnattachConfirm) +} + +func handlerUnattachAsk(w http.ResponseWriter, rq *http.Request) { + log.Println(rq.URL) + var ( + hyphaName = HyphaNameFromRq(rq, "unattach-ask") + hd, isOld = HyphaStorage[hyphaName] + hasAmnt = hd != nil && hd.binaryPath != "" + ) + if !hasAmnt { + HttpErr(w, http.StatusBadRequest, hyphaName, "Cannot unattach", "No attachment attached yet, therefore you cannot unattach") + log.Println("Rejected (no amnt):", rq.URL) + return + } else if ok := user.CanProceed(rq, "unattach-confirm"); !ok { + HttpErr(w, http.StatusForbidden, hyphaName, "Not enough rights", "You must be a trusted editor to unattach attachments") + log.Println("Rejected (no rights):", rq.URL) + return + } + util.HTTP200Page(w, base("Unattach "+hyphaName+"?", templates.UnattachAskHTML(rq, hyphaName, isOld))) +} + +func handlerUnattachConfirm(w http.ResponseWriter, rq *http.Request) { + log.Println(rq.URL) + var ( + hyphaName = HyphaNameFromRq(rq, "unattach-confirm") + hyphaData, isOld = HyphaStorage[hyphaName] + hasAmnt = hyphaData != nil && hyphaData.binaryPath != "" + u = user.FromRequest(rq) + ) + if !u.CanProceed("unattach-confirm") { + HttpErr(w, http.StatusForbidden, hyphaName, "Not enough rights", "You must be a trusted editor to unattach attachments") + log.Println("Rejected (no rights):", rq.URL) + return + } + if !hasAmnt { + HttpErr(w, http.StatusBadRequest, hyphaName, "Cannot unattach", "No attachment attached yet, therefore you cannot unattach") + log.Println("Rejected (no amnt):", rq.URL) + return + } else if !isOld { + // The precondition is to have the hypha in the first place. + HttpErr(w, http.StatusPreconditionFailed, hyphaName, + "Error: no such hypha", + "Could not unattach this hypha because it does not exist") + return + } + if hop := hyphaData.UnattachHypha(hyphaName, u); len(hop.Errs) != 0 { + HttpErr(w, http.StatusInternalServerError, hyphaName, + "Error: could not unattach hypha", + fmt.Sprintf("Could not unattach this hypha due to internal errors. Server errors: %v", hop.Errs)) + return + } + http.Redirect(w, rq, "/page/"+hyphaName, http.StatusSeeOther) } func handlerRenameAsk(w http.ResponseWriter, rq *http.Request) { diff --git a/http_readers.go b/http_readers.go index 497a30f..939aef1 100644 --- a/http_readers.go +++ b/http_readers.go @@ -80,6 +80,7 @@ func handlerPage(w http.ResponseWriter, rq *http.Request) { var ( hyphaName = HyphaNameFromRq(rq, "page") data, hyphaExists = HyphaStorage[hyphaName] + hasAmnt = hyphaExists && data.binaryPath != "" contents string openGraph string ) @@ -102,6 +103,7 @@ func handlerPage(w http.ResponseWriter, rq *http.Request) { templates.PageHTML(rq, hyphaName, naviTitle(hyphaName), contents, - treeHTML, prevHypha, nextHypha), + treeHTML, prevHypha, nextHypha, + hasAmnt), openGraph)) } diff --git a/hypha.go b/hypha.go index c9f2612..64015e8 100644 --- a/hypha.go +++ b/hypha.go @@ -128,6 +128,29 @@ func (hd *HyphaData) DeleteHypha(hyphaName string, u *user.User) *history.Histor return hop } +// UnattachHypha unattaches hypha and makes a history record about that. +func (hd *HyphaData) UnattachHypha(hyphaName string, u *user.User) *history.HistoryOp { + hop := history.Operation(history.TypeUnattachHypha). + WithFilesRemoved(hd.binaryPath). + WithMsg(fmt.Sprintf("Unattach ā€˜%s’", hyphaName)). + WithUser(u). + Apply() + if len(hop.Errs) == 0 { + hd, ok := HyphaStorage[hyphaName] + if ok { + if hd.binaryPath != "" { + hd.binaryPath = "" + } + // If nothing is left of the hypha + if hd.textPath == "" { + delete(HyphaStorage, hyphaName) + hyphae.DecrementCount() + } + } + } + return hop +} + func findHyphaeToRename(hyphaName string, recursive bool) []string { hyphae := []string{hyphaName} if recursive { diff --git a/main.go b/main.go index ae1aa0f..6a4c26a 100644 --- a/main.go +++ b/main.go @@ -154,7 +154,7 @@ func main() { history.Start(WikiDir) // See http_readers.go for /page/, /text/, /binary/ - // See http_mutators.go for /upload-binary/, /upload-text/, /edit/, /delete-ask/, /delete-confirm/, /rename-ask/, /rename-confirm/ + // See http_mutators.go for /upload-binary/, /upload-text/, /edit/, /delete-ask/, /delete-confirm/, /rename-ask/, /rename-confirm/, /unattach-ask/, /unattach-confirm/ // See http_auth.go for /login, /login-data, /logout, /logout-confirm // See http_history.go for /history/, /recent-changes http.HandleFunc("/list", handlerList) diff --git a/templates/asset.qtpl.go b/templates/asset.qtpl.go index abab4b6..36a8da9 100644 --- a/templates/asset.qtpl.go +++ b/templates/asset.qtpl.go @@ -72,6 +72,7 @@ article pre.codeblock { padding:.5rem; white-space: pre-wrap; border-radius: .25 .navi-title__separator { margin: 0 .25rem; } .navi-title__colon { margin-right: .5rem; } .upload-amnt { clear: both; padding: .5rem; border-radius: .25rem; } +.upload-amnt__unattach { display: block; } aside { clear: both; } .img-gallery { text-align: center; margin-top: .25rem; margin-bottom: .25rem; } diff --git a/templates/default.css b/templates/default.css index ef42a50..192e2fe 100644 --- a/templates/default.css +++ b/templates/default.css @@ -47,6 +47,7 @@ article pre.codeblock { padding:.5rem; white-space: pre-wrap; border-radius: .25 .navi-title__separator { margin: 0 .25rem; } .navi-title__colon { margin-right: .5rem; } .upload-amnt { clear: both; padding: .5rem; border-radius: .25rem; } +.upload-amnt__unattach { display: block; } aside { clear: both; } .img-gallery { text-align: center; margin-top: .25rem; margin-bottom: .25rem; } diff --git a/templates/http_readers.qtpl b/templates/http_readers.qtpl index cb63f5e..445df90 100644 --- a/templates/http_readers.qtpl +++ b/templates/http_readers.qtpl @@ -28,7 +28,7 @@ {% endfunc %} If `contents` == "", a helpful message is shown instead. -{% func PageHTML(rq *http.Request, hyphaName, naviTitle, contents, tree, prevHyphaName, nextHyphaName string) %} +{% func PageHTML(rq *http.Request, hyphaName, naviTitle, contents, tree, prevHyphaName, nextHyphaName string, hasAmnt bool) %}
{%= navHTML(rq, hyphaName, "page") %}
@@ -51,6 +51,9 @@ If `contents` == "", a helpful message is shown instead.
+ {% if hasAmnt %} + Unattach current attachment? + {% endif %}
diff --git a/templates/http_readers.qtpl.go b/templates/http_readers.qtpl.go index f0c0826..2e52eba 100644 --- a/templates/http_readers.qtpl.go +++ b/templates/http_readers.qtpl.go @@ -144,7 +144,7 @@ func RevisionHTML(rq *http.Request, hyphaName, naviTitle, contents, tree, revHas // If `contents` == "", a helpful message is shown instead. //line templates/http_readers.qtpl:31 -func StreamPageHTML(qw422016 *qt422016.Writer, rq *http.Request, hyphaName, naviTitle, contents, tree, prevHyphaName, nextHyphaName string) { +func StreamPageHTML(qw422016 *qt422016.Writer, rq *http.Request, hyphaName, naviTitle, contents, tree, prevHyphaName, nextHyphaName string, hasAmnt bool) { //line templates/http_readers.qtpl:31 qw422016.N().S(`
@@ -237,50 +237,65 @@ func StreamPageHTML(qw422016 *qt422016.Writer, rq *http.Request, hyphaName, navi qw422016.N().S(`" method="post" enctype="multipart/form-data" class="upload-amnt"> + `) +//line templates/http_readers.qtpl:54 + if hasAmnt { +//line templates/http_readers.qtpl:54 + qw422016.N().S(` + Unattach current attachment? + `) +//line templates/http_readers.qtpl:56 + } +//line templates/http_readers.qtpl:56 + qw422016.N().S(`
`) -//line templates/http_readers.qtpl:59 +//line templates/http_readers.qtpl:62 } -//line templates/http_readers.qtpl:59 +//line templates/http_readers.qtpl:62 qw422016.N().S(`
`) -//line templates/http_readers.qtpl:64 +//line templates/http_readers.qtpl:67 } -//line templates/http_readers.qtpl:64 -func WritePageHTML(qq422016 qtio422016.Writer, rq *http.Request, hyphaName, naviTitle, contents, tree, prevHyphaName, nextHyphaName string) { -//line templates/http_readers.qtpl:64 +//line templates/http_readers.qtpl:67 +func WritePageHTML(qq422016 qtio422016.Writer, rq *http.Request, hyphaName, naviTitle, contents, tree, prevHyphaName, nextHyphaName string, hasAmnt bool) { +//line templates/http_readers.qtpl:67 qw422016 := qt422016.AcquireWriter(qq422016) -//line templates/http_readers.qtpl:64 - StreamPageHTML(qw422016, rq, hyphaName, naviTitle, contents, tree, prevHyphaName, nextHyphaName) -//line templates/http_readers.qtpl:64 +//line templates/http_readers.qtpl:67 + StreamPageHTML(qw422016, rq, hyphaName, naviTitle, contents, tree, prevHyphaName, nextHyphaName, hasAmnt) +//line templates/http_readers.qtpl:67 qt422016.ReleaseWriter(qw422016) -//line templates/http_readers.qtpl:64 +//line templates/http_readers.qtpl:67 } -//line templates/http_readers.qtpl:64 -func PageHTML(rq *http.Request, hyphaName, naviTitle, contents, tree, prevHyphaName, nextHyphaName string) string { -//line templates/http_readers.qtpl:64 +//line templates/http_readers.qtpl:67 +func PageHTML(rq *http.Request, hyphaName, naviTitle, contents, tree, prevHyphaName, nextHyphaName string, hasAmnt bool) string { +//line templates/http_readers.qtpl:67 qb422016 := qt422016.AcquireByteBuffer() -//line templates/http_readers.qtpl:64 - WritePageHTML(qb422016, rq, hyphaName, naviTitle, contents, tree, prevHyphaName, nextHyphaName) -//line templates/http_readers.qtpl:64 +//line templates/http_readers.qtpl:67 + WritePageHTML(qb422016, rq, hyphaName, naviTitle, contents, tree, prevHyphaName, nextHyphaName, hasAmnt) +//line templates/http_readers.qtpl:67 qs422016 := string(qb422016.B) -//line templates/http_readers.qtpl:64 +//line templates/http_readers.qtpl:67 qt422016.ReleaseByteBuffer(qb422016) -//line templates/http_readers.qtpl:64 +//line templates/http_readers.qtpl:67 return qs422016 -//line templates/http_readers.qtpl:64 +//line templates/http_readers.qtpl:67 } diff --git a/templates/unattach.qtpl b/templates/unattach.qtpl new file mode 100644 index 0000000..e4f0ae2 --- /dev/null +++ b/templates/unattach.qtpl @@ -0,0 +1,24 @@ +{% import "net/http" %} +{% func UnattachAskHTML(rq *http.Request, hyphaName string, isOld bool) %} +
+{%= navHTML(rq, hyphaName, "unattach-ask") %} +{%- if isOld -%} +
+

Unattach {%s hyphaName %}?

+

Do you really want to unattach hypha {%s hyphaName %}?

+

Confirm

+

Cancel

+
+{%- else -%} + {%= cannotUnattachDueToNonExistence(hyphaName) %} +{%- endif -%} +
+{% endfunc %} + +{% func cannotUnattachDueToNonExistence(hyphaName string) %} +
+

Cannot unattach {%s hyphaName %}

+

This hypha does not exist.

+

Go back

+
+{% endfunc %} diff --git a/templates/unattach.qtpl.go b/templates/unattach.qtpl.go new file mode 100644 index 0000000..c00bc80 --- /dev/null +++ b/templates/unattach.qtpl.go @@ -0,0 +1,148 @@ +// Code generated by qtc from "unattach.qtpl". DO NOT EDIT. +// See https://github.com/valyala/quicktemplate for details. + +//line templates/unattach.qtpl:1 +package templates + +//line templates/unattach.qtpl:1 +import "net/http" + +//line templates/unattach.qtpl:2 +import ( + qtio422016 "io" + + qt422016 "github.com/valyala/quicktemplate" +) + +//line templates/unattach.qtpl:2 +var ( + _ = qtio422016.Copy + _ = qt422016.AcquireByteBuffer +) + +//line templates/unattach.qtpl:2 +func StreamUnattachAskHTML(qw422016 *qt422016.Writer, rq *http.Request, hyphaName string, isOld bool) { +//line templates/unattach.qtpl:2 + qw422016.N().S(` +
+`) +//line templates/unattach.qtpl:4 + streamnavHTML(qw422016, rq, hyphaName, "unattach-ask") +//line templates/unattach.qtpl:4 + qw422016.N().S(` +`) +//line templates/unattach.qtpl:5 + if isOld { +//line templates/unattach.qtpl:5 + qw422016.N().S(`
+

Unattach `) +//line templates/unattach.qtpl:7 + qw422016.E().S(hyphaName) +//line templates/unattach.qtpl:7 + qw422016.N().S(`?

+

Do you really want to unattach hypha `) +//line templates/unattach.qtpl:8 + qw422016.E().S(hyphaName) +//line templates/unattach.qtpl:8 + qw422016.N().S(`?

+

Confirm

+

Cancel

+
+`) +//line templates/unattach.qtpl:12 + } else { +//line templates/unattach.qtpl:12 + qw422016.N().S(` `) +//line templates/unattach.qtpl:13 + streamcannotUnattachDueToNonExistence(qw422016, hyphaName) +//line templates/unattach.qtpl:13 + qw422016.N().S(` +`) +//line templates/unattach.qtpl:14 + } +//line templates/unattach.qtpl:14 + qw422016.N().S(`
+`) +//line templates/unattach.qtpl:16 +} + +//line templates/unattach.qtpl:16 +func WriteUnattachAskHTML(qq422016 qtio422016.Writer, rq *http.Request, hyphaName string, isOld bool) { +//line templates/unattach.qtpl:16 + qw422016 := qt422016.AcquireWriter(qq422016) +//line templates/unattach.qtpl:16 + StreamUnattachAskHTML(qw422016, rq, hyphaName, isOld) +//line templates/unattach.qtpl:16 + qt422016.ReleaseWriter(qw422016) +//line templates/unattach.qtpl:16 +} + +//line templates/unattach.qtpl:16 +func UnattachAskHTML(rq *http.Request, hyphaName string, isOld bool) string { +//line templates/unattach.qtpl:16 + qb422016 := qt422016.AcquireByteBuffer() +//line templates/unattach.qtpl:16 + WriteUnattachAskHTML(qb422016, rq, hyphaName, isOld) +//line templates/unattach.qtpl:16 + qs422016 := string(qb422016.B) +//line templates/unattach.qtpl:16 + qt422016.ReleaseByteBuffer(qb422016) +//line templates/unattach.qtpl:16 + return qs422016 +//line templates/unattach.qtpl:16 +} + +//line templates/unattach.qtpl:18 +func streamcannotUnattachDueToNonExistence(qw422016 *qt422016.Writer, hyphaName string) { +//line templates/unattach.qtpl:18 + qw422016.N().S(` +
+

Cannot unattach `) +//line templates/unattach.qtpl:20 + qw422016.E().S(hyphaName) +//line templates/unattach.qtpl:20 + qw422016.N().S(`

+

This hypha does not exist.

+

Go back

+
+`) +//line templates/unattach.qtpl:24 +} + +//line templates/unattach.qtpl:24 +func writecannotUnattachDueToNonExistence(qq422016 qtio422016.Writer, hyphaName string) { +//line templates/unattach.qtpl:24 + qw422016 := qt422016.AcquireWriter(qq422016) +//line templates/unattach.qtpl:24 + streamcannotUnattachDueToNonExistence(qw422016, hyphaName) +//line templates/unattach.qtpl:24 + qt422016.ReleaseWriter(qw422016) +//line templates/unattach.qtpl:24 +} + +//line templates/unattach.qtpl:24 +func cannotUnattachDueToNonExistence(hyphaName string) string { +//line templates/unattach.qtpl:24 + qb422016 := qt422016.AcquireByteBuffer() +//line templates/unattach.qtpl:24 + writecannotUnattachDueToNonExistence(qb422016, hyphaName) +//line templates/unattach.qtpl:24 + qs422016 := string(qb422016.B) +//line templates/unattach.qtpl:24 + qt422016.ReleaseByteBuffer(qb422016) +//line templates/unattach.qtpl:24 + return qs422016 +//line templates/unattach.qtpl:24 +} diff --git a/user/user.go b/user/user.go index 331c17f..640e76f 100644 --- a/user/user.go +++ b/user/user.go @@ -15,14 +15,16 @@ type User struct { // Route — Right (more is more right) var minimalRights = map[string]int{ - "edit": 1, - "upload-binary": 1, - "upload-text": 1, - "rename-ask": 2, - "rename-confirm": 2, - "delete-ask": 3, - "delete-confirm": 3, - "reindex": 4, + "edit": 1, + "upload-binary": 1, + "upload-text": 1, + "rename-ask": 2, + "rename-confirm": 2, + "unattach-ask": 2, + "unattach-confirm": 2, + "delete-ask": 3, + "delete-confirm": 3, + "reindex": 4, } // Group — Right