Explorar o código

Working queue page, confirmation reducing data, posting to action.

Frédéric G. MARAND %!s(int64=2) %!d(string=hai) anos
pai
achega
3bfadc9961
Modificáronse 6 ficheiros con 160 adicións e 52 borrados
  1. 61 8
      back/web/confirm.go
  2. 8 9
      back/web/routes.go
  3. 14 9
      front/templates/confirm.gohtml
  4. 19 23
      front/templates/queue.gohtml
  5. 9 1
      go.mod
  6. 49 2
      go.sum

+ 61 - 8
back/web/confirm.go

@@ -3,6 +3,7 @@ package web
 import (
 	"net/http"
 	"net/url"
+	"regexp"
 	"strconv"
 	"strings"
 
@@ -11,6 +12,9 @@ import (
 	"github.com/gin-gonic/gin"
 	"github.com/google/uuid"
 	csrf "github.com/utrack/gin-csrf"
+	"golang.org/x/exp/maps"
+
+	"code.osinet.fr/fgm/sqs_demo/back/services/redriver"
 )
 
 type (
@@ -26,8 +30,9 @@ const (
 	LevelWarning Level = "warning"
 
 	OpDelete  QueueOp = "delete"
-	OpRedrive QueueOp = "redrive"
 	OpInvalid QueueOp = "invalid"
+	OpPurge   QueueOp = "purge"
+	OpRedrive QueueOp = "redrive"
 )
 
 var confirms = map[QueueOp]struct {
@@ -41,16 +46,22 @@ var confirms = map[QueueOp]struct {
 		question:    "Do you confirm this deletion request?",
 		Level:       LevelDanger,
 	},
+	OpInvalid: {
+		description: "The operation requested is invalid",
+		Level:       LevelInfo,
+	},
+	OpPurge: {
+		confirm:     "Purge",
+		description: "All the messages in the deadletter queue will be lost after that step",
+		question:    "Do you confirm this purge 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.
@@ -109,14 +120,55 @@ func parseIDs(u *url.URL, isValid IDValidator) []string {
 	return ids
 }
 
+func parseMessages(u *url.URL, ids []string) []redriver.Message {
+	m := make(map[int]redriver.Message, len(ids))
+	keyRx := regexp.MustCompile("^(id|mid|rh)-(.+)")
+	mOK := make(map[int]bool, len(ids))
+	for k, vs := range u.Query() {
+		ms := keyRx.FindStringSubmatch(k)
+		if len(ms) != 3 || len(vs) != 1 {
+			continue
+		}
+		v := vs[0]
+
+		id, err := strconv.Atoi(ms[2])
+		if err != nil {
+			continue
+		}
+		message := m[id]
+		switch ms[1] {
+		case "id":
+			if v == "on" {
+				mOK[id] = true
+			}
+		case "rh":
+			message.ReceiptHandle = v
+		case "mid":
+			message.MessageId = v
+		}
+		m[id] = message
+	}
+
+	// Only return selected messages.
+	res := make(map[int]redriver.Message, len(mOK))
+	for id := range mOK {
+		res[id] = m[id]
+	}
+	return maps.Values(res)
+}
+
 func makeConfirmHandler() gin.HandlerFunc {
 	return func(c *gin.Context) {
 		u := c.Request.URL
 		op := getOp(u)
 		// For real data, probably use validateUUID or validateARN.
 		ids := parseIDs(u, validateUint)
+		var messages []redriver.Message
 		if len(ids) == 0 {
 			op = OpInvalid
+			messages = nil
+		} else {
+			messages = parseMessages(u, ids)
 		}
 
 		data := confirms[op] // op is guaranteed to be valid by getOp().
@@ -125,9 +177,10 @@ func makeConfirmHandler() gin.HandlerFunc {
 		sess := sessions.Default(c)
 		flashes := sess.Flashes()
 		token := csrf.GetToken(c)
-		sess.Save()
+		_ = sess.Save()
 
 		h := gin.H{
+			"action":      "/queue/" + qName + "/" + string(op),
 			"cancel":      "Cancel",
 			"cancelURL":   "/queue/" + qName,
 			"confirm":     data.confirm,
@@ -135,14 +188,14 @@ func makeConfirmHandler() gin.HandlerFunc {
 			"description": data.description,
 			"flashes":     flashes,
 			"level":       data.Level,
-			"list":        ids,
+			"messages":    messages,
 			"question":    data.question,
 		}
 		if qName == "" {
 			h["cancelURL"] = "/"
 		}
 		if op == OpInvalid {
-			h["list"] = nil
+			h["messages"] = nil
 		}
 		c.HTML(http.StatusOK, "confirm", h)
 	}

+ 8 - 9
back/web/routes.go

@@ -7,13 +7,13 @@ import (
 	"net/http"
 	"path/filepath"
 
+	"github.com/Masterminds/sprig/v3"
 	"github.com/davecgh/go-spew/spew"
 	"github.com/fgm/izidic"
 	"github.com/gin-contrib/sessions"
 	"github.com/gin-contrib/sessions/cookie"
 	"github.com/gin-gonic/gin"
-
-	csrf "github.com/utrack/gin-csrf"
+	"github.com/utrack/gin-csrf"
 
 	"code.osinet.fr/fgm/sqs_demo/back/services"
 	"code.osinet.fr/fgm/sqs_demo/back/services/redriver"
@@ -39,6 +39,7 @@ func SetupRoutes(rd redriver.Redriver, renderer *template.Template, storeSecret,
 	// Done
 	r.StaticFS(assetsPrefix, PrefixFileSystem(assetsPrefix, http.FS(front.Assets)))
 	r.GET("/queue", gin.WrapH(http.RedirectHandler("/", http.StatusMovedPermanently)))
+	r.GET("/queue/:name/confirm", mw, makeConfirmHandler()) // Needs mw to set token.
 
 	// Back done, front WIP
 	r.GET("/queue/:name", makeQueueHandler(rd))
@@ -47,11 +48,9 @@ func SetupRoutes(rd redriver.Redriver, renderer *template.Template, storeSecret,
 	r.GET("/", makeHomeHandler(rd))
 
 	// TODO
-	r.GET("/queue/:name/confirm", mw, makeConfirmHandler())
-	r.POST("/queue/:name/delete", mw, makeDeleteConfirmHandler(rd))
-	r.GET("/queue/:name/purge", mw, makePurgeHandler(rd))
-	r.POST("/queue/:name/purge", mw, makePurgeConfirmHandler(rd))
-	r.POST("/queue/:name/redrive", mw, makeRedriveHandler(rd))
+	r.POST("/queue/:name/delete", mw, makeDeleteConfirmHandler(rd)) // Needs mw to check token.
+	r.POST("/queue/:name/purge", mw, makePurgeConfirmHandler(rd))   // Needs mw to check token.
+	r.POST("/queue/:name/redrive", mw, makeRedriveHandler(rd))      // Needs mw to check token.
 	return r
 }
 
@@ -69,14 +68,14 @@ func RendererService(_ *izidic.Container) (any, error) {
 		"dump": func(args ...any) template.HTML {
 			return "<pre>" + template.HTML(spew.Sdump(args...)) + "</pre>\n"
 		},
-	})
+	}).Funcs(sprig.FuncMap())
 	for _, tpl := range []struct {
 		name  string
 		value string
 	}{
 		{"confirm", front.Confirm},
 		{"flashes", front.Flashes},
-		{"queue-get", front.QueueGet},
+		{"queue-get", front.QueueGet}, // Includes queue-item.
 		{"500", front.Err500},
 	} {
 		renderer, err = renderer.New(tpl.name).Parse(tpl.value)

+ 14 - 9
front/templates/confirm.gohtml

@@ -20,18 +20,23 @@
             <div class="mt-3">
                 <p class="fs-4 text">{{ .description }}</p>
             </div>
-            {{ if .list }}
-                <ul>
-                    {{range .list}}
-                        <li>{{ . }}</li>
-                    {{end}}
-                </ul>
-            {{end}}
-            <form method="post">
+            <form method="post" action="{{ .action }}">
+                {{ if .messages }}
+                    <ul>
+                        {{range $id, $message := .messages}}
+                            <li>
+                                {{ $message.MessageId }}
+                                <input type="hidden" name="id-{{ $id }}" value=""/>
+                                <input type="hidden" name="mid-{{ $id }}" value="{{ $message.MessageId }}"/>
+                                <input type="hidden" name="rh-{{ $id }}" value="{{ $message.ReceiptHandle }}"/>
+                            </li>
+                        {{end}}
+                    </ul>
+                {{end}}
+                <input type="hidden" name="_csrf" value="{{ .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>

+ 19 - 23
front/templates/queue.gohtml

@@ -48,11 +48,6 @@
                     <th scope="row">Info</th>
                     <td>{{ dump .info }}</td>
 
-                </tr>
-                <tr>
-                    <th scope="row">Items</th>
-                    <td>{{ dump .items }}</td>
-
                 </tr>
                 </tbody>
             </table>
@@ -77,26 +72,16 @@
                             <input id="select-all" class="form-check-input mt-0 me-3" type="checkbox">
                             <span>Select all</span>
                         </th>
-                        <th scope="col">Propriété</th>
-                        <th scope="col">Valeur</th>
+                        <th scope="col">ID</th>
+                        <th scope="col">Body</th>
+                        <th scope="col">Attributes</th>
                     </tr>
                     </thead>
                     <tbody>
-                    <tr>
-                        <td><input class="form-check-input checkbox" type="checkbox" name="id-1" id="flexCheckDefault"></td>
-                        <td><label for="flexCheckDefault">info message</label></td>
-                        <td>Some value ?</td>
-                    </tr>
-                    <tr>
-                        <td><input class="form-check-input checkbox" type="checkbox" name="id-2" id="flexCheckChecked"></td>
-                        <td><label for="flexCheckChecked">info message</label></td>
-                        <td>Some value ?</td>
-                    </tr>
-                    <tr>
-                        <td><input class="form-check-input checkbox" type="checkbox" name="id-3" id="flexCheckChecked2"></td>
-                        <td><label for="flexCheckChecked2">info message</label></td>
-                        <td>Some value ?</td>
-                    </tr>
+                    {{ range $i, $message := .items }}
+                        {{ $row := dict "message" $message "i" $i }}
+                        {{ template "queue-item" $row }}
+                    {{ end }}
                     </tbody>
                 </table>
             </form>
@@ -106,4 +91,15 @@
     <script src="/assets/script.js"></script>
     </body>
     </html>
-{{end}}
+{{end}}
+
+{{ define "queue-item" -}}
+    <tr>
+        <th scope="row"><input class="form-check-input checkbox" type="checkbox" name="id-{{ .i }}" />
+        <input type="hidden" name="rh-{{ .i }}" value="{{ .message.ReceiptHandle }}" /></th>
+        <input type="hidden" name="mid-{{ .i }}" value="{{ .message.MessageId }}" /></th>
+        <td>{{ .message.MessageId }}</td>
+        <td>{{ abbrev 80 .message.Body }}</td>
+        <td>{{ toJson .message.MessageAttributes | abbrev 80 }}</td>
+    </tr>
+{{ end }}

+ 9 - 1
go.mod

@@ -3,6 +3,7 @@ module code.osinet.fr/fgm/sqs_demo
 go 1.19
 
 require (
+	github.com/Masterminds/sprig/v3 v3.2.3
 	github.com/aws/aws-sdk-go-v2 v1.17.4
 	github.com/aws/aws-sdk-go-v2/config v1.18.12
 	github.com/aws/aws-sdk-go-v2/service/sqs v1.20.2
@@ -11,13 +12,14 @@ require (
 	github.com/gin-contrib/sessions v0.0.5
 	github.com/gin-gonic/gin v1.8.2
 	github.com/google/uuid v1.3.0
-	github.com/gorilla/mux v1.8.0
 	github.com/utrack/gin-csrf v0.0.0-20190424104817-40fb8d2c8fca
 	golang.org/x/exp v0.0.0-20230206171751-46f607a40771
 	gopkg.in/yaml.v3 v3.0.1
 )
 
 require (
+	github.com/Masterminds/goutils v1.1.1 // indirect
+	github.com/Masterminds/semver/v3 v3.2.0 // indirect
 	github.com/aws/aws-sdk-go-v2/credentials v1.13.12 // indirect
 	github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.12.22 // indirect
 	github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.28 // indirect
@@ -37,13 +39,19 @@ require (
 	github.com/gorilla/context v1.1.1 // indirect
 	github.com/gorilla/securecookie v1.1.1 // indirect
 	github.com/gorilla/sessions v1.2.1 // indirect
+	github.com/huandu/xstrings v1.3.3 // indirect
+	github.com/imdario/mergo v0.3.11 // indirect
 	github.com/json-iterator/go v1.1.12 // indirect
 	github.com/kr/text v0.2.0 // indirect
 	github.com/leodido/go-urn v1.2.1 // indirect
 	github.com/mattn/go-isatty v0.0.17 // indirect
+	github.com/mitchellh/copystructure v1.0.0 // indirect
+	github.com/mitchellh/reflectwalk v1.0.0 // indirect
 	github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
 	github.com/modern-go/reflect2 v1.0.2 // indirect
 	github.com/pelletier/go-toml/v2 v2.0.6 // indirect
+	github.com/shopspring/decimal v1.2.0 // indirect
+	github.com/spf13/cast v1.3.1 // indirect
 	github.com/ugorji/go/codec v1.2.9 // indirect
 	golang.org/x/crypto v0.5.0 // indirect
 	golang.org/x/net v0.5.0 // indirect

+ 49 - 2
go.sum

@@ -1,3 +1,9 @@
+github.com/Masterminds/goutils v1.1.1 h1:5nUrii3FMTL5diU80unEVvNevw1nH4+ZV4DSLVJLSYI=
+github.com/Masterminds/goutils v1.1.1/go.mod h1:8cTjp+g8YejhMuvIA5y2vz3BpJxksy863GQaJW2MFNU=
+github.com/Masterminds/semver/v3 v3.2.0 h1:3MEsd0SM6jqZojhjLWWeBY+Kcjy9i6MQAeY7YgDP83g=
+github.com/Masterminds/semver/v3 v3.2.0/go.mod h1:qvl/7zhW3nngYb5+80sSMF+FG2BjYrf8m9wsX0PNOMQ=
+github.com/Masterminds/sprig/v3 v3.2.3 h1:eL2fZNezLomi0uOLqjQoN6BfsDD+fyLtgbJMAj9n6YA=
+github.com/Masterminds/sprig/v3 v3.2.3/go.mod h1:rXcFaZ2zZbLRJv/xSysmlgIM1u11eBaRMhvYXJNkGuM=
 github.com/aws/aws-sdk-go-v2 v1.17.4 h1:wyC6p9Yfq6V2y98wfDsj6OnNQa4w2BLGCLIxzNhwOGY=
 github.com/aws/aws-sdk-go-v2 v1.17.4/go.mod h1:uzbQtefpm44goOPmdKyAlXSNcwlRgF3ePWVW6EtJvvw=
 github.com/aws/aws-sdk-go-v2/config v1.18.12 h1:fKs/I4wccmfrNRO9rdrbMO1NgLxct6H9rNMiPdBxHWw=
@@ -63,18 +69,21 @@ github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/
 github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
 github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
 github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
+github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
 github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
 github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
 github.com/gorilla/context v1.1.1 h1:AWwleXJkX/nhcU9bZSnZoi3h/qGYqQAGhq6zZe/aQW8=
 github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg=
-github.com/gorilla/mux v1.8.0 h1:i40aqfkR1h2SlN9hojwV5ZA91wcXFOvkdNIeFDP5koI=
-github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So=
 github.com/gorilla/securecookie v1.1.1 h1:miw7JPhV+b/lAHSXz4qd/nN9jRiAFV5FwjeKyCS8BvQ=
 github.com/gorilla/securecookie v1.1.1/go.mod h1:ra0sb63/xPlUeL+yeDciTfxMRAA+MP+HVt/4epWDjd4=
 github.com/gorilla/sessions v1.1.1/go.mod h1:8KCfur6+4Mqcc6S0FEfKuN15Vl5MgXW92AE8ovaJD0w=
 github.com/gorilla/sessions v1.1.3/go.mod h1:8KCfur6+4Mqcc6S0FEfKuN15Vl5MgXW92AE8ovaJD0w=
 github.com/gorilla/sessions v1.2.1 h1:DHd3rPN5lE3Ts3D8rKkQ8x/0kqfeNmBAaiSi+o7FsgI=
 github.com/gorilla/sessions v1.2.1/go.mod h1:dk2InVEVJ0sfLlnXv9EAgkf6ecYs/i80K/zI+bUmuGM=
+github.com/huandu/xstrings v1.3.3 h1:/Gcsuc1x8JVbJ9/rlye4xZnVAbEkGauT8lbebqcQws4=
+github.com/huandu/xstrings v1.3.3/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE=
+github.com/imdario/mergo v0.3.11 h1:3tnifQM4i+fbajXKBHXWEH+KvNHqojZ778UH75j3bGA=
+github.com/imdario/mergo v0.3.11/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA=
 github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo=
 github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U=
 github.com/json-iterator/go v1.1.5/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
@@ -93,6 +102,10 @@ github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNx
 github.com/mattn/go-isatty v0.0.17 h1:BTarxUcIeDqL27Mc+vyvdWYSL28zpIhv3RoTdsLMPng=
 github.com/mattn/go-isatty v0.0.17/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
 github.com/memcachier/mc v2.0.1+incompatible/go.mod h1:7bkvFE61leUBvXz+yxsOnGBQSZpBSPIMUQSmmSHvuXc=
+github.com/mitchellh/copystructure v1.0.0 h1:Laisrj+bAB6b/yJwB5Bt3ITZhGJdqmxquMKeZ+mmkFQ=
+github.com/mitchellh/copystructure v1.0.0/go.mod h1:SNtv71yrdKgLRyLFxmLdkAbkKEFWgYaq1OVrnRcwhnw=
+github.com/mitchellh/reflectwalk v1.0.0 h1:9D+8oIskB4VJBN5SFlmc27fSlIBZaov1Wpk/IfikLNY=
+github.com/mitchellh/reflectwalk v1.0.0/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw=
 github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
 github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
 github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
@@ -105,11 +118,16 @@ github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZb
 github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
 github.com/quasoft/memstore v0.0.0-20180925164028-84a050167438/go.mod h1:wTPjTepVu7uJBYgZ0SdWHQlIas582j6cn2jgk4DDdlg=
 github.com/rogpeppe/go-internal v1.8.0 h1:FCbCCtXNOY3UtUuHUYaghJg4y7Fd14rXifAYUAtL9R8=
+github.com/shopspring/decimal v1.2.0 h1:abSATXmQEYyShuxI4/vyW3tV1MrKAJzCZ/0zLUXYbsQ=
+github.com/shopspring/decimal v1.2.0/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o=
+github.com/spf13/cast v1.3.1 h1:nFm6S0SMdyzrzcmThSipiEubIDy8WEXKNZ0UOgiRpng=
+github.com/spf13/cast v1.3.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
 github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
 github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
 github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
 github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
 github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
+github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
 github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
 github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
 github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
@@ -120,20 +138,48 @@ github.com/ugorji/go/codec v1.2.9 h1:rmenucSohSTiyL09Y+l2OCk+FrMxGMzho2+tjr5ticU
 github.com/ugorji/go/codec v1.2.9/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg=
 github.com/utrack/gin-csrf v0.0.0-20190424104817-40fb8d2c8fca h1:lpvAjPK+PcxnbcB8H7axIb4fMNwjX9bE4DzwPjGg8aE=
 github.com/utrack/gin-csrf v0.0.0-20190424104817-40fb8d2c8fca/go.mod h1:XXKxNbpoLihvvT7orUZbs/iZayg1n4ip7iJakJPAwA8=
+github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
+golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
+golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
+golang.org/x/crypto v0.3.0/go.mod h1:hebNnKkNXi2UzZN1eVRvBB7co0a+JxK6XbPiWVs/3J4=
 golang.org/x/crypto v0.5.0 h1:U/0M97KRkSFvyD/3FSmdP5W5swImpNgle/EHFhOsQPE=
 golang.org/x/crypto v0.5.0/go.mod h1:NK/OQwhpMQP3MwtdjgLlYHnH9ebylxKWv3e0fK+mkQU=
 golang.org/x/exp v0.0.0-20230206171751-46f607a40771 h1:xP7rWLUr1e1n2xkK5YB4LI0hPEy3LJC6Wk+D4pGlOJg=
 golang.org/x/exp v0.0.0-20230206171751-46f607a40771/go.mod h1:CxIveKay+FTh1D0yPZemJVgC/95VzuuOLq5Qi4xnoYc=
+golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
 golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
+golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
+golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
+golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
+golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY=
 golang.org/x/net v0.5.0 h1:GyT4nK/YDHSqa1c4753ouYCDajOYKTja9Xb/OHtgvSw=
 golang.org/x/net v0.5.0/go.mod h1:DivGGAXEgPSlEBzxGzZI+ZLohi+xUj054jfeKui00ws=
 golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sys v0.0.0-20181228144115-9a3f9b0469bb/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.5.0 h1:MUK/U/4lj1t1oPg0HfuXDN/Z1wv31ZJ/YcPiGccS4DU=
 golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
+golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
+golang.org/x/term v0.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc=
+golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
+golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
+golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
+golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
 golang.org/x/text v0.6.0 h1:3XmdazWV+ubf7QgHSTWeykHOci5oeekaGJBLkrkaw4k=
 golang.org/x/text v0.6.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
+golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
+golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
+golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
+golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
 golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
 google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
 google.golang.org/protobuf v1.28.1 h1:d0NfwRgPtno5B1Wa6L2DAG+KivqkdutMf1UhdNx175w=
@@ -146,6 +192,7 @@ gopkg.in/go-playground/validator.v8 v8.18.2/go.mod h1:RX2a/7Ha8BgOhfk7j780h4/u/R
 gopkg.in/mgo.v2 v2.0.0-20180705113604-9856a29383ce/go.mod h1:yeKp02qBN3iKW1OzL3MGk2IdtZzaj7SFntXj72NppTA=
 gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
 gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
+gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
 gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
 gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
 gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=