Browse Source

3-state confirm form working.

Frederic G. MARAND 2 years ago
parent
commit
3bf6836158
3 changed files with 85 additions and 58 deletions
  1. 74 50
      back/web/confirm.go
  2. 10 7
      front/templates/confirm.gohtml
  3. 1 1
      front/templates/queue.gohtml

+ 74 - 50
back/web/confirm.go

@@ -3,21 +3,56 @@ package web
 import (
 	"net/http"
 	"net/url"
+	"strconv"
 	"strings"
 
+	"github.com/aws/aws-sdk-go-v2/aws/arn"
 	"github.com/gin-contrib/sessions"
 	"github.com/gin-gonic/gin"
+	"github.com/google/uuid"
 	csrf "github.com/utrack/gin-csrf"
 )
 
-type QueueOp string
+type (
+	// Level is an information level: danger | warning.
+	Level string
+	// QueueOp is an allowed operation or reserved error: delete|redrive|invalid.
+	QueueOp string
+)
 
 const (
+	LevelDanger  Level = "danger"
+	LevelInfo    Level = "info"
+	LevelWarning Level = "warning"
+
 	OpDelete  QueueOp = "delete"
 	OpRedrive QueueOp = "redrive"
 	OpInvalid QueueOp = "invalid"
 )
 
+var confirms = map[QueueOp]struct {
+	question, description string
+	confirm               string
+	Level                 // danger|warning
+}{
+	OpDelete: {
+		confirm:     "Delete",
+		description: "These messages cannot be recovered after that step",
+		question:    "Do you confirm this deletion request?",
+		Level:       LevelDanger,
+	},
+	OpRedrive: {
+		confirm:     "Redrive",
+		description: "These messages will be in their source queue after that step",
+		question:    "Do you confirm this redriving request?",
+		Level:       LevelWarning,
+	},
+	OpInvalid: {
+		description: "The operation requested is invalid",
+		Level:       LevelInfo,
+	},
+}
+
 // getOp extracts the operation from the unsafe form data, returning a safe value.
 func getOp(u *url.URL) QueueOp {
 	q := u.Query()
@@ -38,7 +73,26 @@ func getOp(u *url.URL) QueueOp {
 	return res
 }
 
-func parseIDs(u *url.URL) []string {
+type IDValidator func(in string) bool
+
+// validateUint is an IDValidator for unsigned integer strings.
+func validateUint(in string) bool {
+	_, err := strconv.Atoi(in)
+	return err == nil
+}
+
+// validateUUID is an IDValidator for UUID strings
+func validateUUID(in string) bool {
+	_, err := uuid.Parse(in)
+	return err == nil
+}
+
+// validateARN is an IDValidator for ARN strings
+func validateARN(in string) bool {
+	return arn.IsARN(in)
+}
+
+func parseIDs(u *url.URL, isValid IDValidator) []string {
 	const prefix = "id-"
 	q := u.Query()
 	var ids []string
@@ -48,78 +102,48 @@ func parseIDs(u *url.URL) []string {
 			continue
 		}
 		b, a, found := strings.Cut(k, prefix)
-		if b == "" && found {
+		if b == "" && found && isValid(a) {
 			ids = append(ids, a)
 		}
 	}
 	return ids
 }
 
-type confirmData struct {
-	question, description string
-	confirm, cancel       string
-	redirect              string // site-relative path
-}
-
 func makeConfirmHandler() gin.HandlerFunc {
 	return func(c *gin.Context) {
 		u := c.Request.URL
 		op := getOp(u)
-		var data confirmData
-		switch op {
-		case OpDelete:
-			data = confirmDelete(c)
-		case OpRedrive:
-			data = confirmRedrive(c)
-		default:
-			data = confirmInvalid(c)
-
+		// For real data, probably use validateUUID or validateARN.
+		ids := parseIDs(u, validateUint)
+		if len(ids) == 0 {
+			op = OpInvalid
 		}
 
-		ids := parseIDs(u)
+		data := confirms[op] // op is guaranteed to be valid by getOp().
+		qName := c.Param("name")
 
 		sess := sessions.Default(c)
 		flashes := sess.Flashes()
 		token := csrf.GetToken(c)
 		sess.Save()
 
-		c.HTML(http.StatusOK, "confirm", gin.H{
+		h := gin.H{
 			"cancel":      "Cancel",
+			"cancelURL":   "/queue/" + qName,
 			"confirm":     data.confirm,
 			"csrf":        token,
 			"description": data.description,
 			"flashes":     flashes,
+			"level":       data.Level,
 			"list":        ids,
 			"question":    data.question,
-			"redirect":    data.redirect,
-		})
-
-	}
-}
-
-func confirmDelete(c *gin.Context) confirmData {
-	qName := c.Param("name")
-
-	cd := confirmData{
-		cancel:      "",
-		confirm:     "Delete",
-		description: "These messages cannot be recovered after that step",
-		question:    "Do you confirm this deletion request ?",
-		redirect:    "/queue/" + qName,
-	}
-	if qName == "" {
-		cd.redirect = "/"
+		}
+		if qName == "" {
+			h["cancelURL"] = "/"
+		}
+		if op == OpInvalid {
+			h["list"] = nil
+		}
+		c.HTML(http.StatusOK, "confirm", h)
 	}
-	return cd
-}
-
-func confirmRedrive(c *gin.Context) confirmData {
-	cd := confirmData{}
-	return cd
-}
-
-func confirmInvalid(c *gin.Context) confirmData {
-	cd := confirmData{}
-	return cd
-
 }

+ 10 - 7
front/templates/confirm.gohtml

@@ -11,10 +11,12 @@
     <main class="container mt-5">
         <h1 class="display-4 py-4">Confirmation needed</h1>
         {{ template "flashes" .flashes }}
-        <div class="border border-danger rounded p-3">
-            <div>
-                <h3 class="text-danger">{{ .question }}</h3>
-            </div>
+        <div class="border border-{{ .level }} rounded p-3">
+            {{ if .question }}
+                <div>
+                    <h3 class="text-{{ .level }}">{{ .question }}</h3>
+                </div>
+            {{ end }}
             <div class="mt-3">
                 <p class="fs-4 text">{{ .description }}</p>
             </div>
@@ -26,9 +28,10 @@
                 </ul>
             {{end}}
             <form method="post">
-                <a href="{{ .redirect }}" class="btn btn-outline-dark btn-lg px-4">{{ .cancel }}</a>
-                <button class="btn btn-warning btn-lg px-5">{{ .confirm }}</button>
-                <input type="hidden" value="{{ .csrf }}" name="_csrf" />
+                <a href="{{ .cancelURL }}" class="btn btn-outline-dark btn-lg px-4">{{ .cancel }}</a>
+                {{ if .confirm -}}
+                    <button class="btn btn-{{ .level }} btn-lg px-5">{{ .confirm }}</button>{{- end }}
+                <input type="hidden" value="{{ .csrf }}" name="_csrf"/>
             </form>
         </div>
     </main>

+ 1 - 1
front/templates/queue.gohtml

@@ -62,7 +62,7 @@
             <form method="get" id="msg-form" action="/queue/{{ .info.Name }}/confirm">
                 <div class="d-flex align-items-center justify-content-between">
                     <h2 class="my-5">Messages</h2>
-                    <button type="submit" form="msg-form" id="redrive" name="redrive" class="btn btn-outline-success" disabled>
+                    <button type="submit" form="msg-form" id="redrive" name="redrive" class="btn btn-outline-warning" disabled>
                         Redrive selection
                     </button>
                     <button type="submit" form="msg-form" id="delete" name="delete" class="btn btn-outline-danger" disabled>