273 lines
8.1 KiB
Go
273 lines
8.1 KiB
Go
// Copyright (c) 2020, Andreas Schneider
|
|
// All rights reserved.
|
|
//
|
|
// Redistribution and use in source and binary forms, with or without
|
|
// modification, are permitted provided that the following conditions are met:
|
|
// * Redistributions of source code must retain the above copyright
|
|
// notice, this list of conditions and the following disclaimer.
|
|
// * Redistributions in binary form must reproduce the above copyright
|
|
// notice, this list of conditions and the following disclaimer in the
|
|
// documentation and/or other materials provided with the distribution.
|
|
// * Neither the name of the <organization> nor the
|
|
// names of its contributors may be used to endorse or promote products
|
|
// derived from this software without specific prior written permission.
|
|
//
|
|
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
|
// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
|
// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
|
// DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> BE LIABLE FOR ANY
|
|
// DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
|
// (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
|
// LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
|
// ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
|
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
|
// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
|
|
package main
|
|
|
|
import (
|
|
"fmt"
|
|
"html/template"
|
|
"net/http"
|
|
"path"
|
|
|
|
"github.com/go-chi/chi"
|
|
uuid "github.com/satori/go.uuid"
|
|
)
|
|
|
|
type webAdminHandler struct {
|
|
router chi.Router
|
|
tplError *template.Template
|
|
tplConfirm *template.Template
|
|
tplIndex *template.Template
|
|
tplUsers *template.Template
|
|
tplShares *template.Template
|
|
tplShareAddUser *template.Template
|
|
tplCreateShare *template.Template
|
|
}
|
|
|
|
func newWebAdminHandler(app *app) *webAdminHandler {
|
|
loadTemplate := func(filename string) *template.Template {
|
|
t, err := template.ParseFiles(
|
|
path.Join("templates", "base.html"),
|
|
path.Join("templates", filename+".html"),
|
|
)
|
|
if err != nil {
|
|
panic(fmt.Sprintf("cannot load template %q: %v", filename, err))
|
|
}
|
|
return t
|
|
}
|
|
|
|
h := &webAdminHandler{
|
|
tplError: loadTemplate("error"),
|
|
tplConfirm: loadTemplate("confirm"),
|
|
tplIndex: loadTemplate("index"),
|
|
tplUsers: loadTemplate("users"),
|
|
tplShares: loadTemplate("shares"),
|
|
tplShareAddUser: loadTemplate("share-add-user"),
|
|
tplCreateShare: loadTemplate("create-share"),
|
|
}
|
|
|
|
renderPage := func(w http.ResponseWriter, tmpl *template.Template, model interface{}) {
|
|
if err := tmpl.Execute(w, model); err != nil {
|
|
http.Error(w, err.Error(), http.StatusInternalServerError)
|
|
}
|
|
}
|
|
|
|
renderError := func(w http.ResponseWriter, msg, returnURL string) {
|
|
model := struct {
|
|
ErrorMessage string
|
|
ReturnURL string
|
|
}{
|
|
ErrorMessage: msg,
|
|
ReturnURL: returnURL,
|
|
}
|
|
|
|
renderPage(w, h.tplError, model)
|
|
}
|
|
|
|
const confirmRequested = 0
|
|
const confirmAccepted = 1
|
|
const confirmDenied = 2
|
|
confirm := func(w http.ResponseWriter, r *http.Request, msg template.HTML, returnURL string) int {
|
|
if r.FormValue("_yes") != "" {
|
|
return confirmAccepted
|
|
} else if r.FormValue("_no") != "" {
|
|
return confirmDenied
|
|
} else {
|
|
model := struct {
|
|
URL string
|
|
Fields map[string]string
|
|
Message template.HTML
|
|
}{
|
|
URL: returnURL,
|
|
Fields: make(map[string]string),
|
|
Message: msg,
|
|
}
|
|
|
|
for k, v := range r.Form {
|
|
model.Fields[k] = v[0]
|
|
}
|
|
|
|
renderPage(w, h.tplConfirm, model)
|
|
return confirmRequested
|
|
}
|
|
}
|
|
|
|
r := chi.NewRouter()
|
|
r.Get("/", func(w http.ResponseWriter, r *http.Request) {
|
|
renderPage(w, h.tplIndex, map[string]string{"foo": "bar"})
|
|
})
|
|
r.Get("/users", func(w http.ResponseWriter, r *http.Request) {
|
|
users, err := app.userStore.GetUsers()
|
|
if err != nil {
|
|
http.Error(w, err.Error(), http.StatusInternalServerError)
|
|
return
|
|
}
|
|
|
|
renderPage(w, h.tplUsers, users)
|
|
})
|
|
r.Get("/shares", func(w http.ResponseWriter, r *http.Request) {
|
|
shares, err := app.shareStore.GetShares()
|
|
if err != nil {
|
|
http.Error(w, err.Error(), http.StatusInternalServerError)
|
|
return
|
|
}
|
|
|
|
type shareInfo struct {
|
|
Share
|
|
Users []ShareUser
|
|
}
|
|
|
|
shareInfos := make([]shareInfo, len(shares))
|
|
for i := range shares {
|
|
shareInfos[i].Share = shares[i]
|
|
users, err := app.shareStore.GetShareUsers(shares[i])
|
|
if err != nil {
|
|
http.Error(w, err.Error(), http.StatusInternalServerError)
|
|
return
|
|
}
|
|
shareInfos[i].Users = users
|
|
}
|
|
|
|
renderPage(w, h.tplShares, shareInfos)
|
|
})
|
|
r.Route("/share-add-user", func(r chi.Router) {
|
|
r.Get("/", func(w http.ResponseWriter, r *http.Request) {
|
|
shareId := r.URL.Query().Get("share")
|
|
if shareId == "" {
|
|
http.Error(w, "invalid share id", http.StatusBadRequest)
|
|
return
|
|
}
|
|
|
|
users, err := app.userStore.GetUsers()
|
|
if err != nil {
|
|
http.Error(w, err.Error(), http.StatusInternalServerError)
|
|
return
|
|
}
|
|
|
|
model := map[string]interface{}{
|
|
"ShareId": shareId,
|
|
"Users": users,
|
|
}
|
|
|
|
renderPage(w, h.tplShareAddUser, model)
|
|
})
|
|
r.Post("/", func(w http.ResponseWriter, r *http.Request) {
|
|
returnURL := "share-add-user?share=" + r.FormValue("share")
|
|
|
|
shareId, err := uuid.FromString(r.FormValue("share"))
|
|
if err != nil {
|
|
renderError(w, "Internal error: "+err.Error(), "")
|
|
return
|
|
}
|
|
share := Share{UUID: shareId}
|
|
|
|
user, err := app.userStore.GetUser(r.FormValue("user"))
|
|
if err == ErrUserNotFound {
|
|
renderError(w, "User not found.", returnURL)
|
|
return
|
|
} else if err != nil {
|
|
renderError(w, "Internal error: "+err.Error(), "")
|
|
return
|
|
}
|
|
|
|
err = app.shareStore.AddUserToShare(share, user.Username, ShareRole(r.FormValue("role")))
|
|
if err != nil {
|
|
renderError(w, "Cannot add user to share: "+err.Error(), returnURL)
|
|
return
|
|
}
|
|
|
|
http.Redirect(w, r, "shares", http.StatusFound)
|
|
})
|
|
})
|
|
r.Post("/share-delete-user", func(w http.ResponseWriter, r *http.Request) {
|
|
returnURL := "shares"
|
|
|
|
shareId, err := uuid.FromString(r.FormValue("share"))
|
|
if err != nil {
|
|
renderError(w, "Internal error: "+err.Error(), "")
|
|
return
|
|
}
|
|
share := Share{UUID: shareId}
|
|
|
|
err = app.shareStore.RemoveUserFromShare(share, r.FormValue("user"))
|
|
if err != nil {
|
|
renderError(w, "Cannot remove user from share: "+err.Error(), returnURL)
|
|
return
|
|
}
|
|
|
|
http.Redirect(w, r, "shares", http.StatusFound)
|
|
})
|
|
r.Route("/create-share", func(r chi.Router) {
|
|
r.Get("/", func(w http.ResponseWriter, r *http.Request) {
|
|
renderPage(w, h.tplCreateShare, nil)
|
|
})
|
|
r.Post("/", func(w http.ResponseWriter, r *http.Request) {
|
|
share, err := app.shareStore.CreateShare()
|
|
if err != nil {
|
|
renderError(w, "Cannot create share: "+err.Error(), "")
|
|
return
|
|
}
|
|
|
|
share.Name = r.FormValue("name")
|
|
share.Description = r.FormValue("description")
|
|
|
|
if err := app.shareStore.UpdateShareAttributes(share); err != nil {
|
|
renderError(w, "Cannot update share: "+err.Error(), "")
|
|
return
|
|
}
|
|
|
|
http.Redirect(w, r, "shares#share-"+share.UUID.String(), http.StatusFound)
|
|
})
|
|
})
|
|
r.Post("/delete-share", func(w http.ResponseWriter, r *http.Request) {
|
|
share, err := app.shareStore.GetShare(r.FormValue("share"))
|
|
if err != nil {
|
|
renderError(w, "Internal error: "+err.Error(), "")
|
|
return
|
|
}
|
|
|
|
message := fmt.Sprintf(`You are about to delete the share %s (%s).<br/>
|
|
This will delete all data permanently.<br/><br/>
|
|
Are you sure you want to continue?`, share.UUID, share.Name)
|
|
if confirmStatus := confirm(w, r, template.HTML(message), "delete-share"); confirmStatus == confirmRequested {
|
|
// We have already rendered. Nothing to do.
|
|
return
|
|
} else if confirmStatus == confirmAccepted {
|
|
if err := app.shareStore.RemoveShare(share.UUID); err != nil {
|
|
renderError(w, "Share cannot be removed: "+err.Error(), "shares")
|
|
return
|
|
}
|
|
}
|
|
http.Redirect(w, r, "shares", http.StatusFound)
|
|
})
|
|
h.router = r
|
|
|
|
return h
|
|
}
|
|
|
|
func (h webAdminHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
|
h.router.ServeHTTP(w, r)
|
|
}
|