├── preview.png ├── .env.example ├── templates ├── generate.go ├── page_templ.txt ├── page.templ ├── page_templ.go ├── chat_templ.txt ├── chat.templ └── chat_templ.go ├── .github ├── dependabot.yml └── workflows │ └── release_build.yml ├── fly.toml ├── .gitignore ├── Dockerfile ├── limiter.go ├── user └── user.go ├── README.md ├── go.mod ├── chat └── chat.go ├── main.go ├── LICENSE └── go.sum /preview.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mgjules/chat-demo/HEAD/preview.png -------------------------------------------------------------------------------- /.env.example: -------------------------------------------------------------------------------- 1 | JWT_SECRET="d9b95e29-a92a-5e03-8ac0-984e1c39be94" 2 | HTTP_PORT="8080" -------------------------------------------------------------------------------- /templates/generate.go: -------------------------------------------------------------------------------- 1 | package templates 2 | 3 | //go:generate templ generate 4 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: "gomod" # See documentation for possible values 4 | directory: "/" # Location of package manifests 5 | schedule: 6 | interval: "weekly" 7 | -------------------------------------------------------------------------------- /fly.toml: -------------------------------------------------------------------------------- 1 | # fly.toml app configuration file generated for chat-demo-z3h9ea on 2025-03-02T16:31:20Z 2 | # 3 | # See https://fly.io/docs/reference/configuration/ for information about how to use this file. 4 | # 5 | 6 | app = 'chat-demo-z3h9ea' 7 | primary_region = 'sin' 8 | 9 | [build] 10 | 11 | [http_service] 12 | internal_port = 8080 13 | force_https = true 14 | auto_stop_machines = 'stop' 15 | auto_start_machines = true 16 | min_machines_running = 0 17 | processes = ['app'] 18 | 19 | [[vm]] 20 | memory = '256mb' 21 | cpu_kind = 'shared' 22 | cpus = 1 23 | memory_mb = 256 24 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # If you prefer the allow list template instead of the deny list, see community template: 2 | # https://github.com/github/gitignore/blob/main/community/Golang/Go.AllowList.gitignore 3 | # 4 | # Binaries for programs and plugins 5 | *.exe 6 | *.exe~ 7 | *.dll 8 | *.so 9 | *.dylib 10 | 11 | # Test binary, built with `go test -c` 12 | *.test 13 | 14 | # Output of the go coverage tool, specifically when used with LiteIDE 15 | *.out 16 | 17 | # Dependency directories (remove the comment below to include it) 18 | # vendor/ 19 | 20 | # Go workspace file 21 | go.work 22 | 23 | # Environment variables 24 | .env 25 | 26 | # Temporary directory 27 | tmp/* -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM --platform=$BUILDPLATFORM golang:1.23-alpine AS builder 2 | 3 | ARG TARGETPLATFORM 4 | ARG BUILDPLATFORM 5 | ARG TARGETOS 6 | ARG TARGETARCH 7 | 8 | # Add git, curl and upx support 9 | RUN apk add --no-cache git curl upx ca-certificates 10 | 11 | WORKDIR /src 12 | 13 | # Pull modules 14 | COPY go.* ./ 15 | RUN go mod download 16 | 17 | # Copy code into image 18 | COPY . ./ 19 | 20 | # Build application for deployment 21 | RUN --mount=type=cache,target=/root/.cache/go-build \ 22 | --mount=type=cache,target=/go/pkg \ 23 | CGO_ENABLED=0 GOOS=$TARGETOS GOARCH=$TARGETARCH go build -trimpath -ldflags '-s -w' -o /tmp/chatter . 24 | 25 | # Compress binary 26 | RUN upx --best --lzma /tmp/chatter 27 | 28 | # Create minimal image 29 | FROM --platform=$TARGETPLATFORM gcr.io/distroless/base 30 | 31 | # Add the binary 32 | COPY --from=builder /tmp/chatter /chatter 33 | 34 | EXPOSE 80/tcp 35 | 36 | ENTRYPOINT ["/chatter"] 37 | -------------------------------------------------------------------------------- /limiter.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "sync" 5 | "time" 6 | 7 | mlimiters "github.com/mennanov/limiters" 8 | "github.com/mgjules/chat-demo/user" 9 | ) 10 | 11 | type limiters struct { 12 | mu sync.RWMutex 13 | limiters map[string]*mlimiters.TokenBucket 14 | } 15 | 16 | func newLimiters() *limiters { 17 | return &limiters{ 18 | limiters: make(map[string]*mlimiters.TokenBucket), 19 | } 20 | } 21 | 22 | func (l *limiters) add(u *user.User, d time.Duration, b int64) *mlimiters.TokenBucket { 23 | l.mu.RLock() 24 | limiter, found := l.limiters[u.ID.String()] 25 | l.mu.RUnlock() 26 | if found { 27 | return limiter 28 | } 29 | 30 | limiter = mlimiters.NewTokenBucket(b, d, mlimiters.NewLockNoop(), mlimiters.NewTokenBucketInMemory(), mlimiters.NewSystemClock(), mlimiters.NewStdLogger()) 31 | l.mu.Lock() 32 | l.limiters[u.ID.String()] = limiter 33 | l.mu.Unlock() 34 | 35 | return limiter 36 | } 37 | 38 | func (l *limiters) remove(u *user.User) { 39 | l.mu.Lock() 40 | delete(l.limiters, u.ID.String()) 41 | l.mu.Unlock() 42 | } 43 | -------------------------------------------------------------------------------- /templates/page_templ.txt: -------------------------------------------------------------------------------- 1 | Chat Demo 2 | -------------------------------------------------------------------------------- /user/user.go: -------------------------------------------------------------------------------- 1 | package user 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | 7 | "github.com/go-faker/faker/v4" 8 | "github.com/go-faker/faker/v4/pkg/options" 9 | "github.com/rs/xid" 10 | ) 11 | 12 | type userContextKey string 13 | 14 | const userCtxKey userContextKey = "user" 15 | 16 | // User holds information about a user. 17 | type User struct { 18 | ID xid.ID 19 | Name string 20 | } 21 | 22 | // New creates a new User. 23 | func New() *User { 24 | id := xid.New() 25 | // Prevents faker from tracking duplicates since it does that in a non-threadsafe manner. 26 | // Instead we seed the Name with sections of the ID. 27 | nonunique := options.WithGenerateUniqueValues(false) 28 | return &User{ 29 | ID: id, 30 | Name: fmt.Sprintf("%s %s (%s%s)", 31 | faker.FirstName(nonunique), faker.LastName(nonunique), id.String()[4:8], id.String()[15:], 32 | ), 33 | } 34 | } 35 | 36 | // AddToContext adds a user to the context. 37 | func AddToContext(ctx context.Context, user *User) context.Context { 38 | return context.WithValue(ctx, userCtxKey, user) 39 | } 40 | 41 | // FromContext retrieves a user from the context. 42 | func FromContext(ctx context.Context) *User { 43 | u, ok := ctx.Value(userCtxKey).(*User) 44 | if !ok { 45 | return nil 46 | } 47 | 48 | return u 49 | } 50 | -------------------------------------------------------------------------------- /.github/workflows/release_build.yml: -------------------------------------------------------------------------------- 1 | name: release_build 2 | 3 | on: 4 | push: 5 | tags: 6 | - "v*.*.*" 7 | 8 | jobs: 9 | docker: 10 | runs-on: ubuntu-latest 11 | steps: 12 | - name: Checkout 13 | uses: actions/checkout@v3 14 | - name: Docker meta 15 | id: meta 16 | uses: docker/metadata-action@v4 17 | with: 18 | # list of Docker images to use as base name for tags 19 | images: | 20 | julesmike/chatter 21 | # generate Docker tags based on the following events/attributes 22 | tags: | 23 | type=semver,pattern=v{{version}} 24 | type=semver,pattern=v{{major}}.{{minor}} 25 | type=semver,pattern=v{{major}} 26 | - name: Set up QEMU 27 | uses: docker/setup-qemu-action@v2 28 | - name: Set up Docker Buildx 29 | uses: docker/setup-buildx-action@v2 30 | - name: Login to Docker Hub 31 | uses: docker/login-action@v2 32 | with: 33 | username: ${{ secrets.DOCKERHUB_USERNAME }} 34 | password: ${{ secrets.DOCKERHUB_TOKEN }} 35 | - name: Build and push 36 | uses: docker/build-push-action@v3 37 | with: 38 | context: . 39 | platforms: linux/amd64,linux/arm64 40 | push: true 41 | tags: ${{ steps.meta.outputs.tags }} 42 | labels: ${{ steps.meta.outputs.labels }} 43 | cache-from: type=gha 44 | cache-to: type=gha,mode=max 45 | -------------------------------------------------------------------------------- /templates/page.templ: -------------------------------------------------------------------------------- 1 | package templates 2 | 3 | import ( 4 | "github.com/mgjules/chat-demo/chat" 5 | "github.com/mgjules/chat-demo/user" 6 | ) 7 | 8 | templ Page(user *user.User, room *chat.Room, cErr *chat.Error) { 9 | 10 | 11 | 12 | 13 | 14 | Chat Demo 15 | 16 | 21 | 45 | 46 | 47 | @Chat(user, room, cErr) 48 | 49 | 50 | } 51 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # chat-demo 2 | 3 | Enn demonstrasion chat websocket ki servi HTMX, Templ, AlpineJS ek Go. 4 | 5 | ![Preview](preview.png) 6 | 7 | ## Fonksionalite 8 | 9 | - Kominikasion an tem reel par websocket 10 | - Limitasion pou anpes abuse 11 | - Otantifikasion itilizater avek JWT 12 | - Design responsive 13 | - Filtraz bann mo vilain 14 | 15 | ## Teknologi Itilize 16 | 17 | - **Backend**: Go 18 | - **Frontend**: 19 | - [HTMX](https://htmx.org/) - Extensionn HTML pou konteni dinamik 20 | - [Templ](https://github.com/a-h/templ) - Template HTML avek sekirite tip pou Go 21 | - [AlpineJS](https://alpinejs.dev/) - Framework JavaScript lezer 22 | 23 | ## Seki ou bizin avan 24 | 25 | - Go 1.23+ 26 | - Docker (opsionel) 27 | 28 | ## Instalasion 29 | 30 | ### Developman Lokal 31 | 32 | 1. Clone repository-la: 33 | ```bash 34 | git clone https://github.com/mgjules/chat-demo.git 35 | cd chat-demo 36 | ``` 37 | 38 | 2. Kree enn fichie `.env`: 39 | ```bash 40 | HTTP_PORT=8080 41 | JWT_SECRET=to_kle_sekre 42 | ``` 43 | 44 | 3. Roul aplikasion-la: 45 | ```bash 46 | go run . 47 | ``` 48 | 49 | 4. Ale lor http://localhost:8080 50 | 51 | ### Docker 52 | 53 | 1. Konstrwir ek roul container Docker: 54 | ```bash 55 | docker build -t chat-demo . 56 | docker run -p 8080:8080 -e JWT_SECRET=to_kle_sekre chat-demo 57 | ``` 58 | 59 | 2. Ale lor http://localhost:8080 60 | 61 | ## Koman Servi 62 | 63 | 1. Ale lor http://localhost:8080 64 | 2. Ou pou otomatikman koneekte avek enn itilizater au azar 65 | 3. Kumans koze avek lezot itilizater an tem reel 66 | 67 | ## Lisans 68 | 69 | Sa proze-la ena lisans Apache License 2.0 - get fichie [LICENSE](LICENSE) pou plis detay. -------------------------------------------------------------------------------- /templates/page_templ.go: -------------------------------------------------------------------------------- 1 | // Code generated by templ - DO NOT EDIT. 2 | 3 | // templ: version: v0.3.833 4 | package templates 5 | 6 | //lint:file-ignore SA4006 This context is only used if a nested component is present. 7 | 8 | import "github.com/a-h/templ" 9 | import templruntime "github.com/a-h/templ/runtime" 10 | 11 | import ( 12 | "github.com/mgjules/chat-demo/chat" 13 | "github.com/mgjules/chat-demo/user" 14 | ) 15 | 16 | func Page(user *user.User, room *chat.Room, cErr *chat.Error) templ.Component { 17 | return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) { 18 | templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context 19 | if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil { 20 | return templ_7745c5c3_CtxErr 21 | } 22 | templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W) 23 | if !templ_7745c5c3_IsBuffer { 24 | defer func() { 25 | templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer) 26 | if templ_7745c5c3_Err == nil { 27 | templ_7745c5c3_Err = templ_7745c5c3_BufErr 28 | } 29 | }() 30 | } 31 | ctx = templ.InitializeContext(ctx) 32 | templ_7745c5c3_Var1 := templ.GetChildren(ctx) 33 | if templ_7745c5c3_Var1 == nil { 34 | templ_7745c5c3_Var1 = templ.NopComponent 35 | } 36 | ctx = templ.ClearChildren(ctx) 37 | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 1, "Chat Demo") 38 | if templ_7745c5c3_Err != nil { 39 | return templ_7745c5c3_Err 40 | } 41 | templ_7745c5c3_Err = Chat(user, room, cErr).Render(ctx, templ_7745c5c3_Buffer) 42 | if templ_7745c5c3_Err != nil { 43 | return templ_7745c5c3_Err 44 | } 45 | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 2, "") 46 | if templ_7745c5c3_Err != nil { 47 | return templ_7745c5c3_Err 48 | } 49 | return nil 50 | }) 51 | } 52 | 53 | var _ = templruntime.GeneratedTemplate 54 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/mgjules/chat-demo 2 | 3 | go 1.23 4 | 5 | toolchain go1.23.6 6 | 7 | require ( 8 | github.com/TwiN/go-away v1.6.13 9 | github.com/a-h/templ v0.3.833 10 | github.com/enescakir/emoji v1.0.0 11 | github.com/go-chi/chi/v5 v5.0.10 12 | github.com/go-chi/jwtauth/v5 v5.3.0 13 | github.com/go-faker/faker/v4 v4.2.0 14 | github.com/joho/godotenv v1.5.1 15 | github.com/lestrrat-go/jwx/v2 v2.0.18 16 | github.com/mennanov/limiters v1.4.1 17 | github.com/rs/xid v1.5.0 18 | golang.org/x/exp v0.0.0-20230905200255-921286631fa9 19 | golang.org/x/net v0.33.0 20 | golang.org/x/sync v0.10.0 21 | ) 22 | 23 | require ( 24 | github.com/alessandro-c/gomemcached-lock v1.0.0 // indirect 25 | github.com/armon/go-metrics v0.4.1 // indirect 26 | github.com/aws/aws-sdk-go-v2 v1.21.0 // indirect 27 | github.com/aws/aws-sdk-go-v2/feature/dynamodb/attributevalue v1.10.39 // indirect 28 | github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.41 // indirect 29 | github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.4.35 // indirect 30 | github.com/aws/aws-sdk-go-v2/service/dynamodb v1.21.5 // indirect 31 | github.com/aws/aws-sdk-go-v2/service/dynamodbstreams v1.15.5 // indirect 32 | github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.9.14 // indirect 33 | github.com/aws/aws-sdk-go-v2/service/internal/endpoint-discovery v1.7.35 // indirect 34 | github.com/aws/smithy-go v1.14.2 // indirect 35 | github.com/bradfitz/gomemcache v0.0.0-20230905024940-24af94b03874 // indirect 36 | github.com/cenkalti/backoff/v3 v3.2.2 // indirect 37 | github.com/cespare/xxhash/v2 v2.2.0 // indirect 38 | github.com/coreos/go-semver v0.3.1 // indirect 39 | github.com/coreos/go-systemd/v22 v22.5.0 // indirect 40 | github.com/decred/dcrd/dcrec/secp256k1/v4 v4.2.0 // indirect 41 | github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect 42 | github.com/fatih/color v1.16.0 // indirect 43 | github.com/go-redsync/redsync/v4 v4.9.4 // indirect 44 | github.com/goccy/go-json v0.10.2 // indirect 45 | github.com/gogo/protobuf v1.3.2 // indirect 46 | github.com/golang/protobuf v1.5.3 // indirect 47 | github.com/hashicorp/consul/api v1.24.0 // indirect 48 | github.com/hashicorp/errwrap v1.1.0 // indirect 49 | github.com/hashicorp/go-cleanhttp v0.5.2 // indirect 50 | github.com/hashicorp/go-hclog v1.5.0 // indirect 51 | github.com/hashicorp/go-immutable-radix v1.3.1 // indirect 52 | github.com/hashicorp/go-multierror v1.1.1 // indirect 53 | github.com/hashicorp/go-rootcerts v1.0.2 // indirect 54 | github.com/hashicorp/golang-lru v1.0.2 // indirect 55 | github.com/hashicorp/serf v0.10.1 // indirect 56 | github.com/jmespath/go-jmespath v0.4.0 // indirect 57 | github.com/lestrrat-go/blackmagic v1.0.2 // indirect 58 | github.com/lestrrat-go/httpcc v1.0.1 // indirect 59 | github.com/lestrrat-go/httprc v1.0.4 // indirect 60 | github.com/lestrrat-go/iter v1.0.2 // indirect 61 | github.com/lestrrat-go/option v1.0.1 // indirect 62 | github.com/mattn/go-colorable v0.1.13 // indirect 63 | github.com/mattn/go-isatty v0.0.20 // indirect 64 | github.com/mitchellh/go-homedir v1.1.0 // indirect 65 | github.com/mitchellh/mapstructure v1.5.0 // indirect 66 | github.com/pkg/errors v0.9.1 // indirect 67 | github.com/redis/go-redis/v9 v9.1.0 // indirect 68 | github.com/samuel/go-zookeeper v0.0.0-20201211165307-7117e9ea2414 // indirect 69 | github.com/segmentio/asm v1.2.0 // indirect 70 | github.com/thanhpk/randstr v1.0.4 // indirect 71 | go.etcd.io/etcd/api/v3 v3.5.9 // indirect 72 | go.etcd.io/etcd/client/pkg/v3 v3.5.9 // indirect 73 | go.etcd.io/etcd/client/v3 v3.5.9 // indirect 74 | go.uber.org/multierr v1.11.0 // indirect 75 | go.uber.org/zap v1.26.0 // indirect 76 | golang.org/x/crypto v0.31.0 // indirect 77 | golang.org/x/sys v0.28.0 // indirect 78 | golang.org/x/text v0.21.0 // indirect 79 | google.golang.org/genproto v0.0.0-20230913181813-007df8e322eb // indirect 80 | google.golang.org/genproto/googleapis/api v0.0.0-20230913181813-007df8e322eb // indirect 81 | google.golang.org/genproto/googleapis/rpc v0.0.0-20230913181813-007df8e322eb // indirect 82 | google.golang.org/grpc v1.58.3 // indirect 83 | google.golang.org/protobuf v1.33.0 // indirect 84 | ) 85 | -------------------------------------------------------------------------------- /templates/chat_templ.txt: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 |
5 |
7 |
9 |
10 |
11 |
12 |
13 |
Chatroom Demo
14 |
15 |
16 |
17 |
18 |
  • 20 |
    21 |
    22 |
    24 |
  • 26 | 28 |
    29 |
    31 |
    32 |
    33 |
    38 |
    Copyright (c) 39 | . All rights reserved.
    -------------------------------------------------------------------------------- /chat/chat.go: -------------------------------------------------------------------------------- 1 | package chat 2 | 3 | import ( 4 | "container/ring" 5 | "fmt" 6 | "strings" 7 | "sync" 8 | "time" 9 | 10 | goaway "github.com/TwiN/go-away" 11 | "github.com/enescakir/emoji" 12 | "github.com/mgjules/chat-demo/user" 13 | "github.com/rs/xid" 14 | "golang.org/x/exp/slog" 15 | "golang.org/x/net/websocket" 16 | "golang.org/x/sync/semaphore" 17 | ) 18 | 19 | const ( 20 | maxMessageSize uint16 = 256 21 | maxSendWorker uint16 = 1000 22 | maxClients uint16 = 1000 23 | ) 24 | 25 | // List of chat errors. 26 | var ( 27 | ErrLoading = NewError(ErrorSeverityWarning, true, "loading...") 28 | ErrUnknown = NewError(ErrorSeverityError, false, "unknown error") 29 | ErrRateLimited = NewError(ErrorSeverityWarning, false, "please slow down") 30 | ErrMessageEmpty = NewError(ErrorSeverityError, false, "message content cannot be empty") 31 | ErrExistingSession = NewError(ErrorSeverityError, true, "you already have a running session for this room") 32 | ErrRoomFull = NewError(ErrorSeverityError, true, "the room is full") 33 | ) 34 | 35 | // ErrorSeverity is the severity of an error. 36 | type ErrorSeverity uint8 37 | 38 | // List of chat errors. 39 | const ( 40 | ErrorSeverityWarning ErrorSeverity = iota 41 | ErrorSeverityError 42 | ) 43 | 44 | // Error represents a chat error. 45 | type Error struct { 46 | err string 47 | severity ErrorSeverity 48 | global bool 49 | } 50 | 51 | // NewError creates a new Error. 52 | func NewError(s ErrorSeverity, global bool, err string) Error { 53 | return Error{severity: s, global: global, err: err} 54 | } 55 | 56 | // IsError checks if the error of severity error. 57 | func (e Error) IsError() bool { return e.severity == ErrorSeverityError } 58 | 59 | // IsWarning checks if the error of severity warning. 60 | func (e Error) IsWarning() bool { return e.severity == ErrorSeverityWarning } 61 | 62 | // IsGlobal returns true if the error is a global error. 63 | func (e Error) IsGlobal() bool { return e.global } 64 | 65 | // Error implements the Error interface. 66 | func (e Error) Error() string { 67 | return e.err 68 | } 69 | 70 | // Message represents a single chat message. 71 | type Message struct { 72 | User *user.User 73 | Content string 74 | Time time.Time 75 | } 76 | 77 | // NewMessage creates a new Message. 78 | func NewMessage(u *user.User, content string) (*Message, error) { 79 | content = strings.TrimSpace(content) 80 | if content == "" { 81 | return nil, ErrMessageEmpty 82 | } 83 | 84 | rc := []rune(content) 85 | if len(rc) > int(maxMessageSize) { 86 | content = string(rc[:maxMessageSize]) + "..." 87 | } 88 | 89 | content = goaway.Censor(emoji.Parse(content)) 90 | 91 | return &Message{ 92 | User: u, 93 | Content: content, 94 | Time: time.Now().UTC(), 95 | }, nil 96 | } 97 | 98 | // Client represents the relationship between a user and websocket connections. 99 | type Client struct { 100 | user *user.User 101 | conn *websocket.Conn 102 | } 103 | 104 | // Room holds the state of a single chat room. 105 | type Room struct { 106 | muClients sync.RWMutex 107 | clients map[string]*Client 108 | 109 | muMessages sync.RWMutex 110 | messages *ring.Ring 111 | sem *semaphore.Weighted 112 | } 113 | 114 | // NewRoom creates a new Room. 115 | func NewRoom() *Room { 116 | return &Room{ 117 | clients: make(map[string]*Client), 118 | messages: ring.New(100), 119 | sem: semaphore.NewWeighted(int64(maxSendWorker)), 120 | } 121 | } 122 | 123 | // AddClient adds a client along with its websocket connection. 124 | func (r *Room) AddClient(u *user.User, ws *websocket.Conn) error { 125 | if _, found := r.GetClient(u.ID); found { 126 | return ErrExistingSession 127 | } 128 | 129 | if r.IsAtCapacity() { 130 | return ErrRoomFull 131 | } 132 | 133 | r.muClients.Lock() 134 | r.clients[u.ID.String()] = &Client{ 135 | user: u, 136 | conn: ws, 137 | } 138 | r.muClients.Unlock() 139 | 140 | return nil 141 | } 142 | 143 | // IsAtCapacity returns true if the room is at capacity. 144 | func (r *Room) IsAtCapacity() bool { 145 | return r.NumUsers() >= uint64(maxClients) 146 | } 147 | 148 | // GetClient gets a client. 149 | func (r *Room) GetClient(id xid.ID) (*Client, bool) { 150 | r.muClients.RLock() 151 | defer r.muClients.RUnlock() 152 | client, found := r.clients[id.String()] 153 | return client, found 154 | } 155 | 156 | // RemoveClient removes a client. 157 | func (r *Room) RemoveClient(id xid.ID) bool { 158 | if _, found := r.GetClient(id); !found { 159 | return false 160 | } 161 | 162 | r.muClients.Lock() 163 | delete(r.clients, id.String()) 164 | r.muClients.Unlock() 165 | 166 | return true 167 | } 168 | 169 | // NumUsers return the current number of users as clients. 170 | func (r *Room) NumUsers() uint64 { 171 | r.muClients.RLock() 172 | defer r.muClients.RUnlock() 173 | 174 | return uint64(len(r.clients)) 175 | } 176 | 177 | // AddMessage adds a new chat message. 178 | func (r *Room) AddMessage(m *Message) { 179 | r.muMessages.Lock() 180 | r.messages.Value = m 181 | r.messages = r.messages.Next() 182 | r.muMessages.Unlock() 183 | } 184 | 185 | // Messages returns the list of messages. 186 | func (r *Room) Messages() []*Message { 187 | r.muMessages.RLock() 188 | defer r.muMessages.RUnlock() 189 | 190 | messages := make([]*Message, 0) 191 | r.messages.Do(func(m any) { 192 | messages = append(messages, m.(*Message)) 193 | }) 194 | 195 | return messages 196 | } 197 | 198 | // Write implements the io.Writer interface. 199 | func (r *Room) Write(p []byte) (int, error) { 200 | r.IterateClients(func(u *user.User, conn *websocket.Conn) error { 201 | if _, err := conn.Write(p); err != nil { 202 | return fmt.Errorf("write: %w", err) 203 | } 204 | 205 | return nil 206 | }) 207 | 208 | return len(p), nil 209 | } 210 | 211 | // IterateClients executes a function fn 212 | // (e.g. a custom send mechanism or personalized messages per client) for all the clients. 213 | func (r *Room) IterateClients(fn func(u *user.User, conn *websocket.Conn) error) { 214 | r.muClients.RLock() 215 | defer r.muClients.RUnlock() 216 | 217 | var wg sync.WaitGroup 218 | for _, c := range r.clients { 219 | if err := r.sem.Acquire(c.conn.Request().Context(), 1); err != nil { 220 | slog.WarnContext(c.conn.Request().Context(), "acquire lock", "err", err, "user.id", c.user.ID) 221 | continue 222 | } 223 | 224 | wg.Add(1) 225 | go func(c *Client) { 226 | defer func() { 227 | r.sem.Release(1) 228 | wg.Done() 229 | }() 230 | 231 | if err := fn(c.user, c.conn); err != nil { 232 | slog.WarnContext(c.conn.Request().Context(), "send message", "err", "user.id", c.user.ID) 233 | } 234 | }(c) 235 | } 236 | 237 | wg.Wait() 238 | } 239 | -------------------------------------------------------------------------------- /templates/chat.templ: -------------------------------------------------------------------------------- 1 | package templates 2 | 3 | import ( 4 | "strconv" 5 | "time" 6 | 7 | "github.com/mgjules/chat-demo/chat" 8 | "github.com/mgjules/chat-demo/user" 9 | ) 10 | 11 | templ Chat(user *user.User, room *chat.Room, cErr *chat.Error) { 12 | 78 |
    79 | @ChatGlobalError(cErr) 80 |
    81 | @ChatHeader(room.NumUsers(), user.Name) 82 | @ChatMessages(user, room.Messages()) 83 | @ChatForm(cErr) 84 | @ChatFooter() 85 |
    86 |
    87 | } 88 | 89 | templ ChatGlobalError(cErr *chat.Error) { 90 |
    91 | if cErr != nil && cErr.IsGlobal() { 92 |
    93 |
    94 | { cErr.Error() } 95 |
    96 | } 97 |
    98 | } 99 | 100 | templ ChatHeaderNumUsers(numUsers uint64) { 101 |
    { strconv.Itoa(int(numUsers)) + " " + ternary(numUsers > 1, "users", "user") }
    102 | } 103 | 104 | templ ChatHeader(numUsers uint64, userName string) { 105 |
    106 |
    107 |
    108 |
    109 |
    Chatroom Demo
    110 |
    111 | @ChatHeaderNumUsers(numUsers) 112 |
    113 |
    { userName }
    114 |
    115 | } 116 | 117 | templ ChatMessageWrapped(user *user.User, message *chat.Message) { 118 |
    119 | @ChatMessage(user, message) 120 |
    121 | } 122 | 123 | templ ChatMessage(user *user.User, message *chat.Message) { 124 |
  • 125 |
    126 | if user.ID != message.User.ID { 127 |
    { message.User.Name }
    128 | } 129 |
    130 |
    { message.Content }
    131 |
    132 |
    133 |
    134 |
  • 135 | } 136 | 137 | templ ChatMessages(user *user.User, messages []*chat.Message) { 138 | 144 | } 145 | 146 | templ ChatForm(cErr *chat.Error) { 147 |
    148 |
    149 |
    150 | if cErr != nil && !cErr.IsGlobal() { 151 |
    { cErr.Error() }
    152 | } 153 |
    154 | 165 |
    166 |
    167 | } 168 | 169 | templ ChatFooter() { 170 |
    Copyright (c) { time.Now().Format("2006") }. All rights reserved.
    171 | } 172 | 173 | func ternary(cond bool, str1, str2 string) string { 174 | if cond { 175 | return str1 176 | } 177 | 178 | return str2 179 | } 180 | -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | "io" 7 | "net/http" 8 | "os" 9 | "time" 10 | 11 | "github.com/go-chi/chi/v5" 12 | "github.com/go-chi/chi/v5/middleware" 13 | "github.com/go-chi/jwtauth/v5" 14 | "github.com/go-faker/faker/v4" 15 | "github.com/go-faker/faker/v4/pkg/options" 16 | "github.com/joho/godotenv" 17 | "github.com/lestrrat-go/jwx/v2/jwt" 18 | mlimiters "github.com/mennanov/limiters" 19 | "github.com/mgjules/chat-demo/chat" 20 | "github.com/mgjules/chat-demo/templates" 21 | "github.com/mgjules/chat-demo/user" 22 | "github.com/rs/xid" 23 | "golang.org/x/exp/slog" 24 | "golang.org/x/net/websocket" 25 | ) 26 | 27 | func main() { 28 | logger := slog.New(slog.NewTextHandler(os.Stdout, nil)) 29 | slog.SetDefault(logger) 30 | 31 | if err := run(); err != nil { 32 | slog.Error("Failed to start server", "err", err) 33 | os.Exit(1) 34 | } 35 | } 36 | 37 | func run() error { 38 | // Load .env file is present. 39 | godotenv.Load() 40 | 41 | secret := os.Getenv("JWT_SECRET") 42 | if secret == "" { 43 | return errors.New("missing JWT_SECRET environment variable") 44 | } 45 | 46 | port := os.Getenv("HTTP_PORT") 47 | if port == "" { 48 | port = "8080" 49 | } 50 | 51 | jwt := jwtauth.New("HS256", []byte(secret), nil) 52 | 53 | r := chi.NewRouter() 54 | r.Use(middleware.Recoverer) 55 | r.Use(middleware.RealIP) 56 | r.Use(middleware.CleanPath) 57 | r.Use(middleware.StripSlashes) 58 | r.Use(middleware.Compress(5)) 59 | r.Use(middleware.RequestSize(32000)) 60 | r.Use(middleware.Heartbeat("/ping")) 61 | r.Use(jwtauth.Verifier(jwt)) 62 | 63 | room := chat.NewRoom() 64 | // Seeding random messages in room. 65 | for i := 0; i < 1000; i++ { 66 | msg, _ := chat.NewMessage( 67 | user.New(), 68 | faker.Sentence(options.WithGenerateUniqueValues(false)), 69 | ) 70 | room.AddMessage(msg) 71 | } 72 | 73 | lims := newLimiters() 74 | 75 | // Protected routes. 76 | r.Group(func(r chi.Router) { 77 | r.Use(protected) 78 | 79 | r.Get("/", index(room)) 80 | r.Handle("/chatroom", websocket.Handler(chatroom(room, lims))) 81 | }) 82 | 83 | r.Get("/login", login(jwt)) 84 | 85 | server := &http.Server{ 86 | Addr: ":" + port, 87 | Handler: r, 88 | ReadTimeout: 5 * time.Second, 89 | WriteTimeout: 10 * time.Second, 90 | } 91 | slog.Info("Running server...", "addr", "http://"+server.Addr) 92 | return server.ListenAndServe() 93 | } 94 | 95 | func login(auth *jwtauth.JWTAuth) http.HandlerFunc { 96 | return func(w http.ResponseWriter, r *http.Request) { 97 | token, _, err := jwtauth.FromContext(r.Context()) 98 | if err == nil && token != nil && jwt.Validate(token) == nil { 99 | http.Redirect(w, r, "/", http.StatusFound) 100 | return 101 | } 102 | 103 | // Create a fake user and use it as claim to encode a jwt token. 104 | _, t, err := auth.Encode(map[string]any{ 105 | "user": user.New(), 106 | }) 107 | if err != nil { 108 | http.Error(w, err.Error(), http.StatusInternalServerError) 109 | return 110 | } 111 | 112 | http.SetCookie(w, &http.Cookie{ 113 | Name: "jwt", 114 | Value: t, 115 | Expires: time.Now().Add(1 * time.Hour), 116 | Secure: false, 117 | HttpOnly: false, 118 | Path: "/", 119 | }) 120 | 121 | http.Redirect(w, r, "/", http.StatusFound) 122 | } 123 | } 124 | 125 | func protected(next http.Handler) http.Handler { 126 | return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 127 | token, claims, err := jwtauth.FromContext(r.Context()) 128 | if err != nil || token == nil || jwt.Validate(token) != nil { 129 | http.Redirect(w, r, "/login", http.StatusFound) 130 | return 131 | } 132 | 133 | // Retrieve the user from the claims and add it to the request context. 134 | // If the user ID is invalid, we attempt login again. 135 | // This could lead to an infinite loop if a user has a newer claim format. 136 | u := claims["user"].(map[string]any) 137 | id, err := xid.FromString(u["ID"].(string)) 138 | if err != nil { 139 | http.Redirect(w, r, "/login", http.StatusFound) 140 | return 141 | } 142 | 143 | ctx := user.AddToContext(r.Context(), &user.User{ 144 | ID: id, 145 | Name: u["Name"].(string), 146 | }) 147 | 148 | next.ServeHTTP(w, r.WithContext(ctx)) 149 | }) 150 | } 151 | 152 | func index(room *chat.Room) http.HandlerFunc { 153 | return func(w http.ResponseWriter, r *http.Request) { 154 | ctx := r.Context() 155 | user := user.FromContext(ctx) 156 | 157 | // We lock the chat until we get a web socket connection. 158 | w.Header().Set("Content-Type", "text/html; charset=utf-8") 159 | if err := templates.Page(user, room, &chat.ErrLoading).Render(ctx, w); err != nil { 160 | slog.ErrorContext(ctx, "render index template", "err", err, "user.id", user.ID) 161 | w.Write([]byte("failed to render index template")) 162 | } 163 | } 164 | } 165 | 166 | type data struct { 167 | Message string `json:"chat_message"` 168 | Headers map[string]string `json:"HEADERS"` 169 | } 170 | 171 | func chatroom(room *chat.Room, lims *limiters) func(ws *websocket.Conn) { 172 | return func(ws *websocket.Conn) { 173 | ws.MaxPayloadBytes = 2 << 10 // 2KB 174 | defer ws.Close() 175 | 176 | // Retrieve user from context. 177 | ctx := ws.Request().Context() 178 | usr := user.FromContext(ctx) 179 | logger := slog.Default().With("user.id", usr.ID) 180 | if err := room.AddClient(usr, ws); err != nil { 181 | // Inform the current user about the error. 182 | var cErr chat.Error 183 | if errors.As(err, &cErr) { 184 | if cErr.IsGlobal() { 185 | if err := templates.ChatGlobalError(&cErr).Render(ctx, ws); err != nil { 186 | logger.ErrorContext(ctx, "render global error template", "err", err) 187 | } 188 | } else { 189 | if err := templates.ChatForm(&cErr).Render(ctx, ws); err != nil { 190 | logger.ErrorContext(ctx, "render form template", "err", err) 191 | } 192 | } 193 | } 194 | 195 | return 196 | } 197 | 198 | // Remove client from room when user disconnects. 199 | defer func() { 200 | room.RemoveClient(usr.ID) 201 | lims.remove(usr) 202 | 203 | // Update number of user online for all users. 204 | if err := templates.ChatHeaderNumUsers(room.NumUsers()).Render(ctx, room); err != nil { 205 | logger.ErrorContext(ctx, "render online template", "err", err) 206 | } 207 | }() 208 | 209 | // Update number of user online for all users. 210 | if err := templates.ChatHeaderNumUsers(room.NumUsers()).Render(ctx, room); err != nil { 211 | logger.ErrorContext(ctx, "render online template", "err", err) 212 | return 213 | } 214 | 215 | // Unlock global lock. 216 | if err := templates.ChatGlobalError(nil).Render(ctx, ws); err != nil { 217 | logger.ErrorContext(ctx, "render global error template", "err", err) 218 | return 219 | } 220 | if err := templates.ChatForm(nil).Render(ctx, ws); err != nil { 221 | logger.ErrorContext(ctx, "render global error template", "err", err) 222 | return 223 | } 224 | 225 | lim := lims.add(usr, 5*time.Second, 3) 226 | 227 | // Receiving and processing client requests. 228 | for { 229 | var d data 230 | if err := websocket.JSON.Receive(ws, &d); err != nil { 231 | if errors.Is(err, io.EOF) { 232 | break 233 | } 234 | 235 | logger.ErrorContext(ctx, "receive message", "err", err) 236 | 237 | // Inform user something went wrong. 238 | if err := templates.ChatGlobalError(&chat.ErrUnknown).Render(ctx, ws); err != nil { 239 | logger.ErrorContext(ctx, "render error template", "err", err) 240 | break 241 | } 242 | 243 | continue 244 | } 245 | 246 | // Rate limit to prevent abuse. 247 | if wait, err := lim.Limit(ctx); errors.Is(err, mlimiters.ErrLimitExhausted) { 248 | // Inform the current user to slow down and 249 | // disable the form until limiter allows. 250 | if err := templates.ChatForm(&chat.ErrRateLimited).Render(ctx, ws); err != nil { 251 | logger.ErrorContext(ctx, "render form template", "err", err) 252 | break 253 | } 254 | 255 | // Wait until user is no more rate-limited 256 | <-time.After(wait) 257 | 258 | // Re-enable the form. 259 | // Clear the error for the current user. 260 | if err := templates.ChatForm(nil).Render(ctx, ws); err != nil { 261 | logger.ErrorContext(ctx, "render form template", "err", err) 262 | break 263 | } 264 | 265 | continue 266 | } 267 | 268 | // Create and add the message to the room. 269 | msg, err := chat.NewMessage(usr, d.Message) 270 | if err != nil { 271 | // Send back an error if we could not create message. 272 | // Could be a validation error. 273 | var cErr chat.Error 274 | if errors.As(err, &cErr) { 275 | if cErr.IsGlobal() { 276 | if err := templates.ChatGlobalError(&cErr).Render(ctx, ws); err != nil { 277 | logger.ErrorContext(ctx, "render global error template", "err", err) 278 | break 279 | } 280 | } else { 281 | if err := templates.ChatForm(&cErr).Render(ctx, ws); err != nil { 282 | logger.ErrorContext(ctx, "render form template", "err", err) 283 | break 284 | } 285 | } 286 | } 287 | 288 | continue 289 | } 290 | room.AddMessage(msg) 291 | 292 | // Broadcast personalized message to all clients including the current user. 293 | room.IterateClients(func(u *user.User, conn *websocket.Conn) error { 294 | if err := templates.ChatMessageWrapped(u, msg).Render(ctx, conn); err != nil { 295 | return fmt.Errorf("render message template: %w", err) 296 | } 297 | 298 | return nil 299 | }) 300 | 301 | // Reset the form and clear the error for the current user. 302 | if err := templates.ChatForm(nil).Render(ctx, ws); err != nil { 303 | logger.ErrorContext(ctx, "render form template", "err", err) 304 | break 305 | } 306 | if err := templates.ChatGlobalError(nil).Render(ctx, ws); err != nil { 307 | logger.ErrorContext(ctx, "render form template", "err", err) 308 | break 309 | } 310 | } 311 | } 312 | } 313 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /templates/chat_templ.go: -------------------------------------------------------------------------------- 1 | // Code generated by templ - DO NOT EDIT. 2 | 3 | // templ: version: v0.3.833 4 | package templates 5 | 6 | //lint:file-ignore SA4006 This context is only used if a nested component is present. 7 | 8 | import "github.com/a-h/templ" 9 | import templruntime "github.com/a-h/templ/runtime" 10 | 11 | import ( 12 | "strconv" 13 | "time" 14 | 15 | "github.com/mgjules/chat-demo/chat" 16 | "github.com/mgjules/chat-demo/user" 17 | ) 18 | 19 | func Chat(user *user.User, room *chat.Room, cErr *chat.Error) templ.Component { 20 | return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) { 21 | templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context 22 | if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil { 23 | return templ_7745c5c3_CtxErr 24 | } 25 | templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W) 26 | if !templ_7745c5c3_IsBuffer { 27 | defer func() { 28 | templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer) 29 | if templ_7745c5c3_Err == nil { 30 | templ_7745c5c3_Err = templ_7745c5c3_BufErr 31 | } 32 | }() 33 | } 34 | ctx = templ.InitializeContext(ctx) 35 | templ_7745c5c3_Var1 := templ.GetChildren(ctx) 36 | if templ_7745c5c3_Var1 == nil { 37 | templ_7745c5c3_Var1 = templ.NopComponent 38 | } 39 | ctx = templ.ClearChildren(ctx) 40 | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 1, "
    ") 41 | if templ_7745c5c3_Err != nil { 42 | return templ_7745c5c3_Err 43 | } 44 | templ_7745c5c3_Err = ChatGlobalError(cErr).Render(ctx, templ_7745c5c3_Buffer) 45 | if templ_7745c5c3_Err != nil { 46 | return templ_7745c5c3_Err 47 | } 48 | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 2, "
    ") 49 | if templ_7745c5c3_Err != nil { 50 | return templ_7745c5c3_Err 51 | } 52 | templ_7745c5c3_Err = ChatHeader(room.NumUsers(), user.Name).Render(ctx, templ_7745c5c3_Buffer) 53 | if templ_7745c5c3_Err != nil { 54 | return templ_7745c5c3_Err 55 | } 56 | templ_7745c5c3_Err = ChatMessages(user, room.Messages()).Render(ctx, templ_7745c5c3_Buffer) 57 | if templ_7745c5c3_Err != nil { 58 | return templ_7745c5c3_Err 59 | } 60 | templ_7745c5c3_Err = ChatForm(cErr).Render(ctx, templ_7745c5c3_Buffer) 61 | if templ_7745c5c3_Err != nil { 62 | return templ_7745c5c3_Err 63 | } 64 | templ_7745c5c3_Err = ChatFooter().Render(ctx, templ_7745c5c3_Buffer) 65 | if templ_7745c5c3_Err != nil { 66 | return templ_7745c5c3_Err 67 | } 68 | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 3, "
    ") 69 | if templ_7745c5c3_Err != nil { 70 | return templ_7745c5c3_Err 71 | } 72 | return nil 73 | }) 74 | } 75 | 76 | func ChatGlobalError(cErr *chat.Error) templ.Component { 77 | return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) { 78 | templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context 79 | if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil { 80 | return templ_7745c5c3_CtxErr 81 | } 82 | templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W) 83 | if !templ_7745c5c3_IsBuffer { 84 | defer func() { 85 | templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer) 86 | if templ_7745c5c3_Err == nil { 87 | templ_7745c5c3_Err = templ_7745c5c3_BufErr 88 | } 89 | }() 90 | } 91 | ctx = templ.InitializeContext(ctx) 92 | templ_7745c5c3_Var2 := templ.GetChildren(ctx) 93 | if templ_7745c5c3_Var2 == nil { 94 | templ_7745c5c3_Var2 = templ.NopComponent 95 | } 96 | ctx = templ.ClearChildren(ctx) 97 | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 4, "
    ") 98 | if templ_7745c5c3_Err != nil { 99 | return templ_7745c5c3_Err 100 | } 101 | if cErr != nil && cErr.IsGlobal() { 102 | var templ_7745c5c3_Var3 = []any{templ.SafeClass(ternary(cErr.IsError(), "text-red", "text-orange")), "absolute z-4 flex flex-col gap-4 justify-center items-center w-screen h-screen px-2 text-center backdrop-blur-lg bg-coolgray-800/70 uppercase"} 103 | templ_7745c5c3_Err = templ.RenderCSSItems(ctx, templ_7745c5c3_Buffer, templ_7745c5c3_Var3...) 104 | if templ_7745c5c3_Err != nil { 105 | return templ_7745c5c3_Err 106 | } 107 | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 5, "
    ") 121 | if templ_7745c5c3_Err != nil { 122 | return templ_7745c5c3_Err 123 | } 124 | var templ_7745c5c3_Var5 = []any{templ.SafeClass(ternary(cErr.IsError(), "i-carbon:error", "i-carbon:warning-alt")), "text-4xl"} 125 | templ_7745c5c3_Err = templ.RenderCSSItems(ctx, templ_7745c5c3_Buffer, templ_7745c5c3_Var5...) 126 | if templ_7745c5c3_Err != nil { 127 | return templ_7745c5c3_Err 128 | } 129 | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 7, "
    ") 143 | if templ_7745c5c3_Err != nil { 144 | return templ_7745c5c3_Err 145 | } 146 | var templ_7745c5c3_Var7 string 147 | templ_7745c5c3_Var7, templ_7745c5c3_Err = templ.JoinStringErrs(cErr.Error()) 148 | if templ_7745c5c3_Err != nil { 149 | return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/chat.templ`, Line: 94, Col: 18} 150 | } 151 | _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var7)) 152 | if templ_7745c5c3_Err != nil { 153 | return templ_7745c5c3_Err 154 | } 155 | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 9, "
    ") 156 | if templ_7745c5c3_Err != nil { 157 | return templ_7745c5c3_Err 158 | } 159 | } 160 | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 10, "
    ") 161 | if templ_7745c5c3_Err != nil { 162 | return templ_7745c5c3_Err 163 | } 164 | return nil 165 | }) 166 | } 167 | 168 | func ChatHeaderNumUsers(numUsers uint64) templ.Component { 169 | return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) { 170 | templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context 171 | if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil { 172 | return templ_7745c5c3_CtxErr 173 | } 174 | templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W) 175 | if !templ_7745c5c3_IsBuffer { 176 | defer func() { 177 | templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer) 178 | if templ_7745c5c3_Err == nil { 179 | templ_7745c5c3_Err = templ_7745c5c3_BufErr 180 | } 181 | }() 182 | } 183 | ctx = templ.InitializeContext(ctx) 184 | templ_7745c5c3_Var8 := templ.GetChildren(ctx) 185 | if templ_7745c5c3_Var8 == nil { 186 | templ_7745c5c3_Var8 = templ.NopComponent 187 | } 188 | ctx = templ.ClearChildren(ctx) 189 | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 11, "
    ") 190 | if templ_7745c5c3_Err != nil { 191 | return templ_7745c5c3_Err 192 | } 193 | var templ_7745c5c3_Var9 string 194 | templ_7745c5c3_Var9, templ_7745c5c3_Err = templ.JoinStringErrs(strconv.Itoa(int(numUsers)) + " " + ternary(numUsers > 1, "users", "user")) 195 | if templ_7745c5c3_Err != nil { 196 | return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/chat.templ`, Line: 101, Col: 147} 197 | } 198 | _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var9)) 199 | if templ_7745c5c3_Err != nil { 200 | return templ_7745c5c3_Err 201 | } 202 | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 12, "
    ") 203 | if templ_7745c5c3_Err != nil { 204 | return templ_7745c5c3_Err 205 | } 206 | return nil 207 | }) 208 | } 209 | 210 | func ChatHeader(numUsers uint64, userName string) templ.Component { 211 | return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) { 212 | templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context 213 | if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil { 214 | return templ_7745c5c3_CtxErr 215 | } 216 | templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W) 217 | if !templ_7745c5c3_IsBuffer { 218 | defer func() { 219 | templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer) 220 | if templ_7745c5c3_Err == nil { 221 | templ_7745c5c3_Err = templ_7745c5c3_BufErr 222 | } 223 | }() 224 | } 225 | ctx = templ.InitializeContext(ctx) 226 | templ_7745c5c3_Var10 := templ.GetChildren(ctx) 227 | if templ_7745c5c3_Var10 == nil { 228 | templ_7745c5c3_Var10 = templ.NopComponent 229 | } 230 | ctx = templ.ClearChildren(ctx) 231 | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 13, "
    Chatroom Demo
    ") 232 | if templ_7745c5c3_Err != nil { 233 | return templ_7745c5c3_Err 234 | } 235 | templ_7745c5c3_Err = ChatHeaderNumUsers(numUsers).Render(ctx, templ_7745c5c3_Buffer) 236 | if templ_7745c5c3_Err != nil { 237 | return templ_7745c5c3_Err 238 | } 239 | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 14, "
    ") 240 | if templ_7745c5c3_Err != nil { 241 | return templ_7745c5c3_Err 242 | } 243 | var templ_7745c5c3_Var11 string 244 | templ_7745c5c3_Var11, templ_7745c5c3_Err = templ.JoinStringErrs(userName) 245 | if templ_7745c5c3_Err != nil { 246 | return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/chat.templ`, Line: 113, Col: 52} 247 | } 248 | _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var11)) 249 | if templ_7745c5c3_Err != nil { 250 | return templ_7745c5c3_Err 251 | } 252 | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 15, "
    ") 253 | if templ_7745c5c3_Err != nil { 254 | return templ_7745c5c3_Err 255 | } 256 | return nil 257 | }) 258 | } 259 | 260 | func ChatMessageWrapped(user *user.User, message *chat.Message) templ.Component { 261 | return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) { 262 | templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context 263 | if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil { 264 | return templ_7745c5c3_CtxErr 265 | } 266 | templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W) 267 | if !templ_7745c5c3_IsBuffer { 268 | defer func() { 269 | templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer) 270 | if templ_7745c5c3_Err == nil { 271 | templ_7745c5c3_Err = templ_7745c5c3_BufErr 272 | } 273 | }() 274 | } 275 | ctx = templ.InitializeContext(ctx) 276 | templ_7745c5c3_Var12 := templ.GetChildren(ctx) 277 | if templ_7745c5c3_Var12 == nil { 278 | templ_7745c5c3_Var12 = templ.NopComponent 279 | } 280 | ctx = templ.ClearChildren(ctx) 281 | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 16, "
    ") 282 | if templ_7745c5c3_Err != nil { 283 | return templ_7745c5c3_Err 284 | } 285 | templ_7745c5c3_Err = ChatMessage(user, message).Render(ctx, templ_7745c5c3_Buffer) 286 | if templ_7745c5c3_Err != nil { 287 | return templ_7745c5c3_Err 288 | } 289 | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 17, "
    ") 290 | if templ_7745c5c3_Err != nil { 291 | return templ_7745c5c3_Err 292 | } 293 | return nil 294 | }) 295 | } 296 | 297 | func ChatMessage(user *user.User, message *chat.Message) templ.Component { 298 | return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) { 299 | templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context 300 | if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil { 301 | return templ_7745c5c3_CtxErr 302 | } 303 | templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W) 304 | if !templ_7745c5c3_IsBuffer { 305 | defer func() { 306 | templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer) 307 | if templ_7745c5c3_Err == nil { 308 | templ_7745c5c3_Err = templ_7745c5c3_BufErr 309 | } 310 | }() 311 | } 312 | ctx = templ.InitializeContext(ctx) 313 | templ_7745c5c3_Var13 := templ.GetChildren(ctx) 314 | if templ_7745c5c3_Var13 == nil { 315 | templ_7745c5c3_Var13 = templ.NopComponent 316 | } 317 | ctx = templ.ClearChildren(ctx) 318 | var templ_7745c5c3_Var14 = []any{templ.KV("flex justify-end", user.ID == message.User.ID), "overflow-anchor-none transition-all"} 319 | templ_7745c5c3_Err = templ.RenderCSSItems(ctx, templ_7745c5c3_Buffer, templ_7745c5c3_Var14...) 320 | if templ_7745c5c3_Err != nil { 321 | return templ_7745c5c3_Err 322 | } 323 | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 18, "
  • ") 337 | if templ_7745c5c3_Err != nil { 338 | return templ_7745c5c3_Err 339 | } 340 | if user.ID != message.User.ID { 341 | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 20, "
    ") 342 | if templ_7745c5c3_Err != nil { 343 | return templ_7745c5c3_Err 344 | } 345 | var templ_7745c5c3_Var16 string 346 | templ_7745c5c3_Var16, templ_7745c5c3_Err = templ.JoinStringErrs(message.User.Name) 347 | if templ_7745c5c3_Err != nil { 348 | return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/chat.templ`, Line: 127, Col: 50} 349 | } 350 | _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var16)) 351 | if templ_7745c5c3_Err != nil { 352 | return templ_7745c5c3_Err 353 | } 354 | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 21, "
    ") 355 | if templ_7745c5c3_Err != nil { 356 | return templ_7745c5c3_Err 357 | } 358 | } 359 | var templ_7745c5c3_Var17 = []any{templ.KV("mt-1", user.ID != message.User.ID), "flex flex-justify-between gap-2"} 360 | templ_7745c5c3_Err = templ.RenderCSSItems(ctx, templ_7745c5c3_Buffer, templ_7745c5c3_Var17...) 361 | if templ_7745c5c3_Err != nil { 362 | return templ_7745c5c3_Err 363 | } 364 | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 22, "
    ") 378 | if templ_7745c5c3_Err != nil { 379 | return templ_7745c5c3_Err 380 | } 381 | var templ_7745c5c3_Var19 string 382 | templ_7745c5c3_Var19, templ_7745c5c3_Err = templ.JoinStringErrs(message.Content) 383 | if templ_7745c5c3_Err != nil { 384 | return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/chat.templ`, Line: 130, Col: 69} 385 | } 386 | _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var19)) 387 | if templ_7745c5c3_Err != nil { 388 | return templ_7745c5c3_Err 389 | } 390 | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 24, "
  • ") 404 | if templ_7745c5c3_Err != nil { 405 | return templ_7745c5c3_Err 406 | } 407 | return nil 408 | }) 409 | } 410 | 411 | func ChatMessages(user *user.User, messages []*chat.Message) templ.Component { 412 | return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) { 413 | templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context 414 | if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil { 415 | return templ_7745c5c3_CtxErr 416 | } 417 | templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W) 418 | if !templ_7745c5c3_IsBuffer { 419 | defer func() { 420 | templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer) 421 | if templ_7745c5c3_Err == nil { 422 | templ_7745c5c3_Err = templ_7745c5c3_BufErr 423 | } 424 | }() 425 | } 426 | ctx = templ.InitializeContext(ctx) 427 | templ_7745c5c3_Var21 := templ.GetChildren(ctx) 428 | if templ_7745c5c3_Var21 == nil { 429 | templ_7745c5c3_Var21 = templ.NopComponent 430 | } 431 | ctx = templ.ClearChildren(ctx) 432 | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 26, "") 443 | if templ_7745c5c3_Err != nil { 444 | return templ_7745c5c3_Err 445 | } 446 | return nil 447 | }) 448 | } 449 | 450 | func ChatForm(cErr *chat.Error) templ.Component { 451 | return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) { 452 | templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context 453 | if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil { 454 | return templ_7745c5c3_CtxErr 455 | } 456 | templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W) 457 | if !templ_7745c5c3_IsBuffer { 458 | defer func() { 459 | templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer) 460 | if templ_7745c5c3_Err == nil { 461 | templ_7745c5c3_Err = templ_7745c5c3_BufErr 462 | } 463 | }() 464 | } 465 | ctx = templ.InitializeContext(ctx) 466 | templ_7745c5c3_Var22 := templ.GetChildren(ctx) 467 | if templ_7745c5c3_Var22 == nil { 468 | templ_7745c5c3_Var22 = templ.NopComponent 469 | } 470 | ctx = templ.ClearChildren(ctx) 471 | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 28, "
    ") 472 | if templ_7745c5c3_Err != nil { 473 | return templ_7745c5c3_Err 474 | } 475 | if cErr != nil && !cErr.IsGlobal() { 476 | var templ_7745c5c3_Var23 = []any{ternary(cErr != nil && cErr.IsError(), "text-red", "text-orange"), "flex-none mt-2 text-xs uppercase text-center"} 477 | templ_7745c5c3_Err = templ.RenderCSSItems(ctx, templ_7745c5c3_Buffer, templ_7745c5c3_Var23...) 478 | if templ_7745c5c3_Err != nil { 479 | return templ_7745c5c3_Err 480 | } 481 | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 29, "
    ") 495 | if templ_7745c5c3_Err != nil { 496 | return templ_7745c5c3_Err 497 | } 498 | var templ_7745c5c3_Var25 string 499 | templ_7745c5c3_Var25, templ_7745c5c3_Err = templ.JoinStringErrs(cErr.Error()) 500 | if templ_7745c5c3_Err != nil { 501 | return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/chat.templ`, Line: 151, Col: 148} 502 | } 503 | _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var25)) 504 | if templ_7745c5c3_Err != nil { 505 | return templ_7745c5c3_Err 506 | } 507 | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 31, "
    ") 508 | if templ_7745c5c3_Err != nil { 509 | return templ_7745c5c3_Err 510 | } 511 | } 512 | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 32, "
    ") 513 | if templ_7745c5c3_Err != nil { 514 | return templ_7745c5c3_Err 515 | } 516 | var templ_7745c5c3_Var26 = []any{templ.KV(ternary(cErr != nil && cErr.IsError(), "border-red", "border-orange"), cErr != nil && !cErr.IsGlobal()), templ.SafeClass("w-full px-3 py-2 text-sm bg-coolgray-700 bg-opacity-70 border-1 border-coolgray-600 outline-none ring-0 focus:ring-1 focus:ring-coolgray-600 transition-all disabled:opacity-40 disabled:cursor-not-allowed rounded-md")} 517 | templ_7745c5c3_Err = templ.RenderCSSItems(ctx, templ_7745c5c3_Buffer, templ_7745c5c3_Var26...) 518 | if templ_7745c5c3_Err != nil { 519 | return templ_7745c5c3_Err 520 | } 521 | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 33, "
    ") 558 | if templ_7745c5c3_Err != nil { 559 | return templ_7745c5c3_Err 560 | } 561 | return nil 562 | }) 563 | } 564 | 565 | func ChatFooter() templ.Component { 566 | return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) { 567 | templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context 568 | if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil { 569 | return templ_7745c5c3_CtxErr 570 | } 571 | templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W) 572 | if !templ_7745c5c3_IsBuffer { 573 | defer func() { 574 | templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer) 575 | if templ_7745c5c3_Err == nil { 576 | templ_7745c5c3_Err = templ_7745c5c3_BufErr 577 | } 578 | }() 579 | } 580 | ctx = templ.InitializeContext(ctx) 581 | templ_7745c5c3_Var29 := templ.GetChildren(ctx) 582 | if templ_7745c5c3_Var29 == nil { 583 | templ_7745c5c3_Var29 = templ.NopComponent 584 | } 585 | ctx = templ.ClearChildren(ctx) 586 | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 38, "
    Copyright (c) ") 587 | if templ_7745c5c3_Err != nil { 588 | return templ_7745c5c3_Err 589 | } 590 | var templ_7745c5c3_Var30 string 591 | templ_7745c5c3_Var30, templ_7745c5c3_Err = templ.JoinStringErrs(time.Now().Format("2006")) 592 | if templ_7745c5c3_Err != nil { 593 | return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/chat.templ`, Line: 170, Col: 108} 594 | } 595 | _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var30)) 596 | if templ_7745c5c3_Err != nil { 597 | return templ_7745c5c3_Err 598 | } 599 | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 39, ". All rights reserved.
    ") 600 | if templ_7745c5c3_Err != nil { 601 | return templ_7745c5c3_Err 602 | } 603 | return nil 604 | }) 605 | } 606 | 607 | func ternary(cond bool, str1, str2 string) string { 608 | if cond { 609 | return str1 610 | } 611 | 612 | return str2 613 | } 614 | 615 | var _ = templruntime.GeneratedTemplate 616 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/DataDog/datadog-go v3.2.0+incompatible/go.mod h1:LButxg5PwREeZtORoXG3tL4fMGNddJ+vMq1mwgfaqoQ= 2 | github.com/TwiN/go-away v1.6.13 h1:aB6l/FPXmA5ds+V7I9zdhxzpsLLUvVtEuS++iU/ZmgE= 3 | github.com/TwiN/go-away v1.6.13/go.mod h1:MpvIC9Li3minq+CGgbgUDvQ9tDaeW35k5IXZrF9MVas= 4 | github.com/a-h/templ v0.3.833 h1:L/KOk/0VvVTBegtE0fp2RJQiBm7/52Zxv5fqlEHiQUU= 5 | github.com/a-h/templ v0.3.833/go.mod h1:cAu4AiZhtJfBjMY0HASlyzvkrtjnHWPeEsyGK2YYmfk= 6 | github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= 7 | github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= 8 | github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= 9 | github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= 10 | github.com/alessandro-c/gomemcached-lock v1.0.0 h1:SkaMW3WUmxHBFSoq/1jF/hVL0atJijPzaLtrvbuLbM4= 11 | github.com/alessandro-c/gomemcached-lock v1.0.0/go.mod h1:m+EMbPuavZH8fC5zy/lEVFHKMAofF+MYYPvOn9yvvKQ= 12 | github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o= 13 | github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY= 14 | github.com/armon/go-metrics v0.4.1 h1:hR91U9KYmb6bLBYLQjyM+3j+rcd/UhE+G78SFnF8gJA= 15 | github.com/armon/go-metrics v0.4.1/go.mod h1:E6amYzXo6aW1tqzoZGT755KkbgrJsSdpwZ+3JqfkOG4= 16 | github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= 17 | github.com/armon/go-radix v1.0.0/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= 18 | github.com/aws/aws-sdk-go-v2 v1.21.0 h1:gMT0IW+03wtYJhRqTVYn0wLzwdnK9sRMcxmtfGzRdJc= 19 | github.com/aws/aws-sdk-go-v2 v1.21.0/go.mod h1:/RfNgGmRxI+iFOB1OeJUyxiU+9s88k3pfHvDagGEp0M= 20 | github.com/aws/aws-sdk-go-v2/config v1.18.17 h1:jwTkhULSrbr/SQA8tfdYqZxpG8YsRycmIXxJcbrqY5E= 21 | github.com/aws/aws-sdk-go-v2/config v1.18.17/go.mod h1:Lj3E7XcxJnxMa+AYo89YiL68s1cFJRGduChynYU67VA= 22 | github.com/aws/aws-sdk-go-v2/credentials v1.13.17 h1:IubQO/RNeIVKF5Jy77w/LfUvmmCxTnk2TP1UZZIMiF4= 23 | github.com/aws/aws-sdk-go-v2/credentials v1.13.17/go.mod h1:K9xeFo1g/YPMguMUD69YpwB4Nyi6W/5wn706xIInJFg= 24 | github.com/aws/aws-sdk-go-v2/feature/dynamodb/attributevalue v1.10.39 h1:DX/r3aNL7pIVn0K5a+ESL0Fw9ti7Rj05pblEiIJtPmQ= 25 | github.com/aws/aws-sdk-go-v2/feature/dynamodb/attributevalue v1.10.39/go.mod h1:oTk09orqXlwSKnKf+UQhy+4Ci7aCo9x8hn0ZvPCLrns= 26 | github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.13.0 h1:/2Cb3SK3xVOQA7Xfr5nCWCo5H3UiNINtsVvVdk8sQqA= 27 | github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.13.0/go.mod h1:neYVaeKr5eT7BzwULuG2YbLhzWZ22lpjKdCybR7AXrQ= 28 | github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.41 h1:22dGT7PneFMx4+b3pz7lMTRyN8ZKH7M2cW4GP9yUS2g= 29 | github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.41/go.mod h1:CrObHAuPneJBlfEJ5T3szXOUkLEThaGfvnhTf33buas= 30 | github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.4.35 h1:SijA0mgjV8E+8G45ltVHs0fvKpTj8xmZJ3VwhGKtUSI= 31 | github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.4.35/go.mod h1:SJC1nEVVva1g3pHAIdCp7QsRIkMmLAgoDquQ9Rr8kYw= 32 | github.com/aws/aws-sdk-go-v2/internal/ini v1.3.31 h1:hf+Vhp5WtTdcSdE+yEcUz8L73sAzN0R+0jQv+Z51/mI= 33 | github.com/aws/aws-sdk-go-v2/internal/ini v1.3.31/go.mod h1:5zUjguZfG5qjhG9/wqmuyHRyUftl2B5Cp6NNxNC6kRA= 34 | github.com/aws/aws-sdk-go-v2/service/dynamodb v1.21.5 h1:EeNQ3bDA6hlx3vifHf7LT/l9dh9w7D2XgCdaD11TRU4= 35 | github.com/aws/aws-sdk-go-v2/service/dynamodb v1.21.5/go.mod h1:X3ThW5RPV19hi7bnQ0RMAiBjZbzxj4rZlj+qdctbMWY= 36 | github.com/aws/aws-sdk-go-v2/service/dynamodbstreams v1.15.5 h1:xoalM/e1YsT6jkLKl6KA9HUiJANwn2ypJsM9lhW2WP0= 37 | github.com/aws/aws-sdk-go-v2/service/dynamodbstreams v1.15.5/go.mod h1:7QtKdGj66zM4g5hPgxHRQgFGLGal4EgwggTw5OZH56c= 38 | github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.9.14 h1:m0QTSI6pZYJTk5WSKx3fm5cNW/DCicVzULBgU/6IyD0= 39 | github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.9.14/go.mod h1:dDilntgHy9WnHXsh7dDtUPgHKEfTJIBUTHM8OWm0f/0= 40 | github.com/aws/aws-sdk-go-v2/service/internal/endpoint-discovery v1.7.35 h1:UKjpIDLVF90RfV88XurdduMoTxPqtGHZMIDYZQM7RO4= 41 | github.com/aws/aws-sdk-go-v2/service/internal/endpoint-discovery v1.7.35/go.mod h1:B3dUg0V6eJesUTi+m27NUkj7n8hdDKYUpxj8f4+TqaQ= 42 | github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.9.24 h1:c5qGfdbCHav6viBwiyDns3OXqhqAbGjfIB4uVu2ayhk= 43 | github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.9.24/go.mod h1:HMA4FZG6fyib+NDo5bpIxX1EhYjrAOveZJY2YR0xrNE= 44 | github.com/aws/aws-sdk-go-v2/service/sso v1.12.5 h1:bdKIX6SVF3nc3xJFw6Nf0igzS6Ff/louGq8Z6VP/3Hs= 45 | github.com/aws/aws-sdk-go-v2/service/sso v1.12.5/go.mod h1:vuWiaDB30M/QTC+lI3Wj6S/zb7tpUK2MSYgy3Guh2L0= 46 | github.com/aws/aws-sdk-go-v2/service/ssooidc v1.14.5 h1:xLPZMyuZ4GuqRCIec/zWuIhRFPXh2UOJdLXBSi64ZWQ= 47 | github.com/aws/aws-sdk-go-v2/service/ssooidc v1.14.5/go.mod h1:QjxpHmCwAg0ESGtPQnLIVp7SedTOBMYy+Slr3IfMKeI= 48 | github.com/aws/aws-sdk-go-v2/service/sts v1.18.6 h1:rIFn5J3yDoeuKCE9sESXqM5POTAhOP1du3bv/qTL+tE= 49 | github.com/aws/aws-sdk-go-v2/service/sts v1.18.6/go.mod h1:48WJ9l3dwP0GSHWGc5sFGGlCkuA82Mc2xnw+T6Q8aDw= 50 | github.com/aws/smithy-go v1.14.2 h1:MJU9hqBGbvWZdApzpvoF2WAIJDbtjK2NDJSiJP7HblQ= 51 | github.com/aws/smithy-go v1.14.2/go.mod h1:Tg+OJXh4MB2R/uN61Ko2f6hTZwB/ZYGOtib8J3gBHzA= 52 | github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= 53 | github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= 54 | github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= 55 | github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs= 56 | github.com/bradfitz/gomemcache v0.0.0-20190913173617-a41fca850d0b/go.mod h1:H0wQNHz2YrLsuXOZozoeDmnHXkNCRmMW0gwFWDfEZDA= 57 | github.com/bradfitz/gomemcache v0.0.0-20230905024940-24af94b03874 h1:N7oVaKyGp8bttX0bfZGmcGkjz7DLQXhAn3DNd3T0ous= 58 | github.com/bradfitz/gomemcache v0.0.0-20230905024940-24af94b03874/go.mod h1:r5xuitiExdLAJ09PR7vBVENGvp4ZuTBeWTGtxuX3K+c= 59 | github.com/bsm/ginkgo/v2 v2.5.0/go.mod h1:AiKlXPm7ItEHNc/2+OkrNG4E0ITzojb9/xWzvQ9XZ9w= 60 | github.com/bsm/ginkgo/v2 v2.9.5 h1:rtVBYPs3+TC5iLUVOis1B9tjLTup7Cj5IfzosKtvTJ0= 61 | github.com/bsm/ginkgo/v2 v2.9.5/go.mod h1:SwYbGRRDovPVboqFv0tPTcG1sN61LM1Z4ARdbAV9g4c= 62 | github.com/bsm/gomega v1.20.0/go.mod h1:JifAceMQ4crZIWYUKrlGcmbN3bqHogVTADMD2ATsbwk= 63 | github.com/bsm/gomega v1.26.0 h1:LhQm+AFcgV2M0WyKroMASzAzCAJVpAxQXv4SaI9a69Y= 64 | github.com/bsm/gomega v1.26.0/go.mod h1:JyEr/xRbxbtgWNi8tIEVPUYZ5Dzef52k01W3YH0H+O0= 65 | github.com/cenkalti/backoff/v3 v3.2.2 h1:cfUAAO3yvKMYKPrvhDuHSwQnhZNk/RMHKdZqKTxfm6M= 66 | github.com/cenkalti/backoff/v3 v3.2.2/go.mod h1:cIeZDE3IrqwwJl6VUwCN6trj1oXrTS4rc0ij+ULvLYs= 67 | github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= 68 | github.com/cespare/xxhash/v2 v2.1.2/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= 69 | github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44= 70 | github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= 71 | github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= 72 | github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= 73 | github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= 74 | github.com/circonus-labs/circonus-gometrics v2.3.1+incompatible/go.mod h1:nmEj6Dob7S7YxXgwXpfOuvO54S+tGdZdw9fuRZt25Ag= 75 | github.com/circonus-labs/circonusllhist v0.1.3/go.mod h1:kMXHVDlOchFAehlya5ePtbp5jckzBHf4XRpQvBOLI+I= 76 | github.com/coreos/go-semver v0.3.1 h1:yi21YpKnrx1gt5R+la8n5WgS0kCrsPp33dmEyHReZr4= 77 | github.com/coreos/go-semver v0.3.1/go.mod h1:irMmmIw/7yzSRPWryHsK7EYSg09caPQL03VsM8rvUec= 78 | github.com/coreos/go-systemd/v22 v22.5.0 h1:RrqgGjYQKalulkV8NGVIfkXQf6YYmOyiJKk8iXXhfZs= 79 | github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= 80 | github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= 81 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 82 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 83 | github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= 84 | github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 85 | github.com/decred/dcrd/crypto/blake256 v1.0.1/go.mod h1:2OfgNZ5wDpcsFmHmCK5gZTPcCXqlm2ArzUIkw9czNJo= 86 | github.com/decred/dcrd/dcrec/secp256k1/v4 v4.2.0 h1:8UrgZ3GkP4i/CLijOJx79Yu+etlyjdBU4sfcs2WYQMs= 87 | github.com/decred/dcrd/dcrec/secp256k1/v4 v4.2.0/go.mod h1:v57UDF4pDQJcEfFUCRop3lJL149eHGSe9Jvczhzjo/0= 88 | github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78= 89 | github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc= 90 | github.com/enescakir/emoji v1.0.0 h1:W+HsNql8swfCQFtioDGDHCHri8nudlK1n5p2rHCJoog= 91 | github.com/enescakir/emoji v1.0.0/go.mod h1:Bt1EKuLnKDTYpLALApstIkAjdDrS/8IAgTkKp+WKFD0= 92 | github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= 93 | github.com/fatih/color v1.9.0/go.mod h1:eQcE1qtQxscV5RaZvpXrrb8Drkc3/DdQ+uUYCNjL+zU= 94 | github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk= 95 | github.com/fatih/color v1.16.0 h1:zmkK9Ngbjj+K0yRhTVONQh1p/HknKYSlNT+vZCzyokM= 96 | github.com/fatih/color v1.16.0/go.mod h1:fL2Sau1YI5c0pdGEVCbKQbLXB6edEj1ZgiY4NijnWvE= 97 | github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= 98 | github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= 99 | github.com/go-chi/chi/v5 v5.0.10 h1:rLz5avzKpjqxrYwXNfmjkrYYXOyLJd37pz53UFHC6vk= 100 | github.com/go-chi/chi/v5 v5.0.10/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8= 101 | github.com/go-chi/jwtauth/v5 v5.3.0 h1:X7RKGks1lrVeIe2omGyz47pNaNjG2YmwlRN5UKhN8qg= 102 | github.com/go-chi/jwtauth/v5 v5.3.0/go.mod h1:2PoGm/KbnzRN9ILY6HFZAI6fTnb1gEZAKogAyqkd6fY= 103 | github.com/go-faker/faker/v4 v4.2.0 h1:dGebOupKwssrODV51E0zbMrv5e2gO9VWSLNC1WDCpWg= 104 | github.com/go-faker/faker/v4 v4.2.0/go.mod h1:F/bBy8GH9NxOxMInug5Gx4WYeG6fHJZ8Ol/dhcpRub4= 105 | github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= 106 | github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= 107 | github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= 108 | github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= 109 | github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= 110 | github.com/go-logr/logr v1.2.3/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= 111 | github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= 112 | github.com/go-redis/redis v6.15.9+incompatible h1:K0pv1D7EQUjfyoMql+r/jZqCLizCGKFlFgcHWWmHQjg= 113 | github.com/go-redis/redis v6.15.9+incompatible/go.mod h1:NAIEuMOZ/fxfXJIrKDQDz8wamY7mA7PouImQ2Jvg6kA= 114 | github.com/go-redis/redis/v7 v7.4.0 h1:7obg6wUoj05T0EpY0o8B59S9w5yeMWql7sw2kwNW1x4= 115 | github.com/go-redis/redis/v7 v7.4.0/go.mod h1:JDNMw23GTyLNC4GZu9njt15ctBQVn7xjRfnwdHj/Dcg= 116 | github.com/go-redis/redis/v8 v8.11.4 h1:kHoYkfZP6+pe04aFTnhDH6GDROa5yJdHJVNxV3F46Tg= 117 | github.com/go-redis/redis/v8 v8.11.4/go.mod h1:2Z2wHZXdQpCDXEGzqMockDpNyYvi2l4Pxt6RJr792+w= 118 | github.com/go-redsync/redsync/v4 v4.9.4 h1:vRmYusI+qF95XSpApHAdeu+RjyDvxBXbMthbc/x148c= 119 | github.com/go-redsync/redsync/v4 v4.9.4/go.mod h1:RqBDXUw0q+u9FJTeD2gMzGtHeSVV93DiqGl10B9Hn/4= 120 | github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= 121 | github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE= 122 | github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU= 123 | github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= 124 | github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= 125 | github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= 126 | github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= 127 | github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= 128 | github.com/golang/mock v1.6.0/go.mod h1:p6yTPP+5HYm5mzsMV8JkE6ZKdX+/wYM6Hr+LicevLPs= 129 | github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= 130 | github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= 131 | github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= 132 | github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= 133 | github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= 134 | github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= 135 | github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= 136 | github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= 137 | github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= 138 | github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= 139 | github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= 140 | github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg= 141 | github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= 142 | github.com/gomodule/redigo v1.8.9 h1:Sl3u+2BI/kk+VEatbj0scLdrFhjPmbxOc1myhDP41ws= 143 | github.com/gomodule/redigo v1.8.9/go.mod h1:7ArFNvsTjH8GMMzB4uy1snslv2BwmginuMs06a1uzZE= 144 | github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= 145 | github.com/google/btree v1.0.1 h1:gK4Kx5IaGY9CD5sPJ36FHiBJ6ZXl0kilRiiCj+jdYp4= 146 | github.com/google/btree v1.0.1/go.mod h1:xXMiIv4Fb/0kKde4SpL7qlzvu5cMJDRkFDxJfI9uaxA= 147 | github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= 148 | github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= 149 | github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 150 | github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 151 | github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 152 | github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= 153 | github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= 154 | github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= 155 | github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= 156 | github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= 157 | github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= 158 | github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I= 159 | github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= 160 | github.com/hashicorp/consul/api v1.24.0 h1:u2XyStA2j0jnCiVUU7Qyrt8idjRn4ORhK6DlvZ3bWhA= 161 | github.com/hashicorp/consul/api v1.24.0/go.mod h1:NZJGRFYruc/80wYowkPFCp1LbGmJC9L8izrwfyVx/Wg= 162 | github.com/hashicorp/consul/sdk v0.14.1 h1:ZiwE2bKb+zro68sWzZ1SgHF3kRMBZ94TwOCFRF4ylPs= 163 | github.com/hashicorp/consul/sdk v0.14.1/go.mod h1:vFt03juSzocLRFo59NkeQHHmQa6+g7oU0pfzdI1mUhg= 164 | github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= 165 | github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I= 166 | github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= 167 | github.com/hashicorp/go-cleanhttp v0.5.0/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= 168 | github.com/hashicorp/go-cleanhttp v0.5.2 h1:035FKYIWjmULyFRBKPs8TBQoi0x6d9G4xc9neXJWAZQ= 169 | github.com/hashicorp/go-cleanhttp v0.5.2/go.mod h1:kO/YDlP8L1346E6Sodw+PrpBSV4/SoxCXGY6BqNFT48= 170 | github.com/hashicorp/go-hclog v1.5.0 h1:bI2ocEMgcVlz55Oj1xZNBsVi900c7II+fWDyV9o+13c= 171 | github.com/hashicorp/go-hclog v1.5.0/go.mod h1:W4Qnvbt70Wk/zYJryRzDRU/4r0kIg0PVHBcfoyhpF5M= 172 | github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= 173 | github.com/hashicorp/go-immutable-radix v1.3.1 h1:DKHmCUm2hRBK510BaiZlwvpD40f8bJFeZnpfm2KLowc= 174 | github.com/hashicorp/go-immutable-radix v1.3.1/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= 175 | github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM= 176 | github.com/hashicorp/go-msgpack v0.5.5 h1:i9R9JSrqIz0QVLz3sz+i3YJdT7TTSLcfLLzJi9aZTuI= 177 | github.com/hashicorp/go-msgpack v0.5.5/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM= 178 | github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk= 179 | github.com/hashicorp/go-multierror v1.1.0/go.mod h1:spPvp8C1qA32ftKqdAHm4hHTbPw+vmowP0z+KUhOZdA= 180 | github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo= 181 | github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM= 182 | github.com/hashicorp/go-retryablehttp v0.5.3/go.mod h1:9B5zBasrRhHXnJnui7y6sL7es7NDiJgTc6Er0maI1Xs= 183 | github.com/hashicorp/go-rootcerts v1.0.2 h1:jzhAVGtqPKbwpyCPELlgNWhE1znq+qwJtW5Oi2viEzc= 184 | github.com/hashicorp/go-rootcerts v1.0.2/go.mod h1:pqUvnprVnM5bf7AOirdbb01K4ccR319Vf4pU3K5EGc8= 185 | github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerXegt+ozgdvDeDU= 186 | github.com/hashicorp/go-sockaddr v1.0.2 h1:ztczhD1jLxIRjVejw8gFomI1BQZOe2WoVOu0SyteCQc= 187 | github.com/hashicorp/go-sockaddr v1.0.2/go.mod h1:rB4wwRAUzs07qva3c5SdrY/NEtAUjGlgmH/UkBUC97A= 188 | github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4= 189 | github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= 190 | github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= 191 | github.com/hashicorp/go-uuid v1.0.3 h1:2gKiV6YVmrJ1i2CKKa9obLvRieoRGviZFL26PcT/Co8= 192 | github.com/hashicorp/go-uuid v1.0.3/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= 193 | github.com/hashicorp/go-version v1.2.1 h1:zEfKbn2+PDgroKdiOzqiE8rsmLqU2uwi5PB5pBJ3TkI= 194 | github.com/hashicorp/go-version v1.2.1/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= 195 | github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= 196 | github.com/hashicorp/golang-lru v1.0.2 h1:dV3g9Z/unq5DpblPpw+Oqcv4dU/1omnb4Ok8iPY6p1c= 197 | github.com/hashicorp/golang-lru v1.0.2/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4= 198 | github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64= 199 | github.com/hashicorp/mdns v1.0.4/go.mod h1:mtBihi+LeNXGtG8L9dX59gAEa12BDtBQSp4v/YAJqrc= 200 | github.com/hashicorp/memberlist v0.5.0 h1:EtYPN8DpAURiapus508I4n9CzHs2W+8NZGbmmR/prTM= 201 | github.com/hashicorp/memberlist v0.5.0/go.mod h1:yvyXLpo0QaGE59Y7hDTsTzDD25JYBZ4mHgHUZ8lrOI0= 202 | github.com/hashicorp/serf v0.10.1 h1:Z1H2J60yRKvfDYAOZLd2MU0ND4AH/WDz7xYHDWQsIPY= 203 | github.com/hashicorp/serf v0.10.1/go.mod h1:yL2t6BqATOLGc5HF7qbFkTfXoPIY0WZdWHfEvMqbG+4= 204 | github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= 205 | github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= 206 | github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9YPoQUg= 207 | github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo= 208 | github.com/jmespath/go-jmespath/internal/testify v1.5.1 h1:shLQSRRSCCPj3f2gpwzGwWFoC7ycTf1rcQZHOlsJ6N8= 209 | github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U= 210 | github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0= 211 | github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4= 212 | github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= 213 | github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= 214 | github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= 215 | github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= 216 | github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= 217 | github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= 218 | github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= 219 | github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= 220 | github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= 221 | github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= 222 | github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= 223 | github.com/lestrrat-go/blackmagic v1.0.2 h1:Cg2gVSc9h7sz9NOByczrbUvLopQmXrfFx//N+AkAr5k= 224 | github.com/lestrrat-go/blackmagic v1.0.2/go.mod h1:UrEqBzIR2U6CnzVyUtfM6oZNMt/7O7Vohk2J0OGSAtU= 225 | github.com/lestrrat-go/httpcc v1.0.1 h1:ydWCStUeJLkpYyjLDHihupbn2tYmZ7m22BGkcvZZrIE= 226 | github.com/lestrrat-go/httpcc v1.0.1/go.mod h1:qiltp3Mt56+55GPVCbTdM9MlqhvzyuL6W/NMDA8vA5E= 227 | github.com/lestrrat-go/httprc v1.0.4 h1:bAZymwoZQb+Oq8MEbyipag7iSq6YIga8Wj6GOiJGdI8= 228 | github.com/lestrrat-go/httprc v1.0.4/go.mod h1:mwwz3JMTPBjHUkkDv/IGJ39aALInZLrhBp0X7KGUZlo= 229 | github.com/lestrrat-go/iter v1.0.2 h1:gMXo1q4c2pHmC3dn8LzRhJfP1ceCbgSiT9lUydIzltI= 230 | github.com/lestrrat-go/iter v1.0.2/go.mod h1:Momfcq3AnRlRjI5b5O8/G5/BvpzrhoFTZcn06fEOPt4= 231 | github.com/lestrrat-go/jwx/v2 v2.0.18 h1:HHZkYS5wWDDyAiNBwztEtDoX07WDhGEdixm8G06R50o= 232 | github.com/lestrrat-go/jwx/v2 v2.0.18/go.mod h1:fAJ+k5eTgKdDqanzCuK6DAt3W7n3cs2/FX7JhQdk83U= 233 | github.com/lestrrat-go/option v1.0.0/go.mod h1:5ZHFbivi4xwXxhxY9XHDe2FHo6/Z7WWmtT7T5nBBp3I= 234 | github.com/lestrrat-go/option v1.0.1 h1:oAzP2fvZGQKWkvHa1/SAcFolBEca1oN+mQ7eooNBEYU= 235 | github.com/lestrrat-go/option v1.0.1/go.mod h1:5ZHFbivi4xwXxhxY9XHDe2FHo6/Z7WWmtT7T5nBBp3I= 236 | github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= 237 | github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= 238 | github.com/mattn/go-colorable v0.1.6/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= 239 | github.com/mattn/go-colorable v0.1.9/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= 240 | github.com/mattn/go-colorable v0.1.12/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4= 241 | github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= 242 | github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= 243 | github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= 244 | github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= 245 | github.com/mattn/go-isatty v0.0.11/go.mod h1:PhnuNfih5lzO57/f3n+odYbM4JtupLOxQOAqxQCu2WE= 246 | github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= 247 | github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= 248 | github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= 249 | github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= 250 | github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= 251 | github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= 252 | github.com/mennanov/limiters v1.4.1 h1:vj5/geKFW6qJrwD473PwTnQUlzUy6QoC6c+ONvu8eqM= 253 | github.com/mennanov/limiters v1.4.1/go.mod h1:eKMAq7NiG8fYL8dABUalZzNe1WvtsId/KvpML+bEWB8= 254 | github.com/miekg/dns v1.1.26/go.mod h1:bPDLeHnStXmXAq1m/Ch/hvfNHr14JKNPMBo3VZKjuso= 255 | github.com/miekg/dns v1.1.41 h1:WMszZWJG0XmzbK9FEmzH2TVcqYzFesusSIB41b8KHxY= 256 | github.com/miekg/dns v1.1.41/go.mod h1:p6aan82bvRIyn+zDIv9xYNUpwa73JcSh9BKwknJysuI= 257 | github.com/mitchellh/cli v1.1.0/go.mod h1:xcISNoH86gajksDmfB23e/pu+B+GeFRMYmoHXxx3xhI= 258 | github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y= 259 | github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= 260 | github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= 261 | github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= 262 | github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= 263 | github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= 264 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= 265 | github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= 266 | github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= 267 | github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= 268 | github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= 269 | github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU= 270 | github.com/oklog/ulid/v2 v2.1.0/go.mod h1:rcEKHmBBKfef9DhnvX7y1HZBYxjXb0cP5ExxNsTT1QQ= 271 | github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= 272 | github.com/onsi/ginkgo v1.10.1/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= 273 | github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk= 274 | github.com/onsi/ginkgo v1.16.4/go.mod h1:dX+/inL/fNMqNlz0e9LfyB9TswhZpCVdJM/Z6Vvnwo0= 275 | github.com/onsi/ginkgo/v2 v2.1.3/go.mod h1:vw5CSIxN1JObi/U8gcbwft7ZxR2dgaR70JSE3/PpL4c= 276 | github.com/onsi/ginkgo/v2 v2.1.4/go.mod h1:um6tUpWM/cxCK3/FK8BXqEiUMUwRgSM4JXG47RKZmLU= 277 | github.com/onsi/ginkgo/v2 v2.1.6/go.mod h1:MEH45j8TBi6u9BMogfbp0stKC5cdGjumZj5Y7AG4VIk= 278 | github.com/onsi/ginkgo/v2 v2.3.0/go.mod h1:Eew0uilEqZmIEZr8JrvYlvOM7Rr6xzTmMV8AyFNU9d0= 279 | github.com/onsi/ginkgo/v2 v2.4.0/go.mod h1:iHkDK1fKGcBoEHT5W7YBq4RFWaQulw+caOMkAt4OrFo= 280 | github.com/onsi/ginkgo/v2 v2.5.0/go.mod h1:Luc4sArBICYCS8THh8v3i3i5CuSZO+RaQRaJoeNwomw= 281 | github.com/onsi/ginkgo/v2 v2.7.0/go.mod h1:yjiuMwPokqY1XauOgju45q3sJt6VzQ/Fict1LFVcsAo= 282 | github.com/onsi/ginkgo/v2 v2.8.0/go.mod h1:6JsQiECmxCa3V5st74AL/AmsV482EDdVrGaVW6z3oYU= 283 | github.com/onsi/gomega v1.7.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= 284 | github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= 285 | github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= 286 | github.com/onsi/gomega v1.16.0/go.mod h1:HnhC7FXeEQY45zxNK3PPoIUhzk/80Xly9PcubAlGdZY= 287 | github.com/onsi/gomega v1.17.0/go.mod h1:HnhC7FXeEQY45zxNK3PPoIUhzk/80Xly9PcubAlGdZY= 288 | github.com/onsi/gomega v1.19.0/go.mod h1:LY+I3pBVzYsTBU1AnDwOSxaYi9WoWiqgwooUqq9yPro= 289 | github.com/onsi/gomega v1.20.1/go.mod h1:DtrZpjmvpn2mPm4YWQa0/ALMDj9v4YxLgojwPeREyVo= 290 | github.com/onsi/gomega v1.21.1/go.mod h1:iYAIXgPSaDHak0LCMA+AWBpIKBr8WZicMxnE8luStNc= 291 | github.com/onsi/gomega v1.22.1/go.mod h1:x6n7VNe4hw0vkyYUM4mjIXx3JbLiPaBPNgB7PRQ1tuM= 292 | github.com/onsi/gomega v1.24.0/go.mod h1:Z/NWtiqwBrwUt4/2loMmHL63EDLnYHmVbuBpDr2vQAg= 293 | github.com/onsi/gomega v1.24.1/go.mod h1:3AOiACssS3/MajrniINInwbfOOtfZvplPzuRSmvt1jM= 294 | github.com/onsi/gomega v1.25.0/go.mod h1:r+zV744Re+DiYCIPRlYOTxn0YkOLcAnW8k1xXdMPGhM= 295 | github.com/onsi/gomega v1.26.0/go.mod h1:r+zV744Re+DiYCIPRlYOTxn0YkOLcAnW8k1xXdMPGhM= 296 | github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= 297 | github.com/pascaldekloe/goe v0.1.0 h1:cBOtyMzM9HTpWjXfbbunk26uA6nG3a8n06Wieeh0MwY= 298 | github.com/pascaldekloe/goe v0.1.0/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= 299 | github.com/pborman/getopt v0.0.0-20170112200414-7148bc3a4c30/go.mod h1:85jBQOZwpVEaDAr341tbn15RS4fCAsIst0qp7i8ex1o= 300 | github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 301 | github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 302 | github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= 303 | github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 304 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 305 | github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= 306 | github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 307 | github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI= 308 | github.com/posener/complete v1.2.3/go.mod h1:WZIdtGGp+qx0sLrYKtIRAruyNpv6hFCicSgv7Sy7s/s= 309 | github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= 310 | github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo= 311 | github.com/prometheus/client_golang v1.4.0/go.mod h1:e9GMxYsXl05ICDXkRhurwBS4Q3OK1iX/F2sw+iXX5zU= 312 | github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= 313 | github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= 314 | github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= 315 | github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= 316 | github.com/prometheus/common v0.9.1/go.mod h1:yhUN8i9wzaXS3w1O07YhxHEBxD+W35wd8bs7vj7HSQ4= 317 | github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= 318 | github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= 319 | github.com/prometheus/procfs v0.0.8/go.mod h1:7Qr8sr6344vo1JqZ6HhLceV9o3AJ1Ff+GxbHq6oeK9A= 320 | github.com/redis/go-redis/v9 v9.0.2/go.mod h1:/xDTe9EF1LM61hek62Poq2nzQSGj0xSrEtEHbBQevps= 321 | github.com/redis/go-redis/v9 v9.1.0 h1:137FnGdk+EQdCbye1FW+qOEcY5S+SpY9T0NiuqvtfMY= 322 | github.com/redis/go-redis/v9 v9.1.0/go.mod h1:urWj3He21Dj5k4TK1y59xH8Uj6ATueP8AH1cY3lZl4c= 323 | github.com/rs/xid v1.5.0 h1:mKX4bl4iPYJtEIxp6CYiUuLQ/8DYMoz0PUdtGgMFRVc= 324 | github.com/rs/xid v1.5.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg= 325 | github.com/rueian/rueidis v0.0.93 h1:cG905akj2+QyHx0x9y4mN0K8vLi6M94QiyoLulXS3l0= 326 | github.com/rueian/rueidis v0.0.93/go.mod h1:lo6LBci0D986usi5Wxjb4RVNaWENKYbHZSnufGJ9bTE= 327 | github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= 328 | github.com/samuel/go-zookeeper v0.0.0-20201211165307-7117e9ea2414 h1:AJNDS0kP60X8wwWFvbLPwDuojxubj9pbfK7pjHw0vKg= 329 | github.com/samuel/go-zookeeper v0.0.0-20201211165307-7117e9ea2414/go.mod h1:gi+0XIa01GRL2eRQVjQkKGqKF3SF9vZR/HnPullcV2E= 330 | github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529 h1:nn5Wsu0esKSJiIVhscUtVbo7ada43DJhG55ua/hjS5I= 331 | github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc= 332 | github.com/segmentio/asm v1.2.0 h1:9BQrFxC+YOHJlTlHGkTrFWf59nbL3XnCoFLTwDCI7ys= 333 | github.com/segmentio/asm v1.2.0/go.mod h1:BqMnlJP91P8d+4ibuonYZw9mfnzI9HfxselHZr5aAcs= 334 | github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= 335 | github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= 336 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 337 | github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 338 | github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= 339 | github.com/stretchr/objx v0.5.0 h1:1zr/of2m5FGMsad5YfcqgdqdWrIhu+EBEJRhR1U7z/c= 340 | github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= 341 | github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= 342 | github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= 343 | github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= 344 | github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= 345 | github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 346 | github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 347 | github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 348 | github.com/stretchr/testify v1.7.2/go.mod h1:R6va5+xMeoiuVRoj+gSkQ7d3FALtqAAGI1FQKckRals= 349 | github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= 350 | github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= 351 | github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= 352 | github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= 353 | github.com/stvp/tempredis v0.0.0-20181119212430-b82af8480203 h1:QVqDTf3h2WHt08YuiTGPZLls0Wq99X9bWd0Q5ZSBesM= 354 | github.com/stvp/tempredis v0.0.0-20181119212430-b82af8480203/go.mod h1:oqN97ltKNihBbwlX8dLpwxCl3+HnXKV/R0e+sRLd9C8= 355 | github.com/thanhpk/randstr v1.0.4 h1:IN78qu/bR+My+gHCvMEXhR/i5oriVHcTB/BJJIRTsNo= 356 | github.com/thanhpk/randstr v1.0.4/go.mod h1:M/H2P1eNLZzlDwAzpkkkUvoyNNMbzRGhESZuEQk3r0U= 357 | github.com/tv42/httpunix v0.0.0-20150427012821-b75d8614f926/go.mod h1:9ESjWnEqriFuLhtthL60Sar/7RFoluCcXsuvEwTV5KM= 358 | github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= 359 | github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= 360 | github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= 361 | github.com/yuin/goldmark v1.4.1/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= 362 | github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= 363 | go.etcd.io/etcd/api/v3 v3.5.9 h1:4wSsluwyTbGGmyjJktOf3wFQoTBIURXHnq9n/G/JQHs= 364 | go.etcd.io/etcd/api/v3 v3.5.9/go.mod h1:uyAal843mC8uUVSLWz6eHa/d971iDGnCRpmKd2Z+X8k= 365 | go.etcd.io/etcd/client/pkg/v3 v3.5.9 h1:oidDC4+YEuSIQbsR94rY9gur91UPL6DnxDCIYd2IGsE= 366 | go.etcd.io/etcd/client/pkg/v3 v3.5.9/go.mod h1:y+CzeSmkMpWN2Jyu1npecjB9BBnABxGM4pN8cGuJeL4= 367 | go.etcd.io/etcd/client/v3 v3.5.9 h1:r5xghnU7CwbUxD/fbUtRyJGaYNfDun8sp/gTr1hew6E= 368 | go.etcd.io/etcd/client/v3 v3.5.9/go.mod h1:i/Eo5LrZ5IKqpbtpPDuaUnDOUv471oDg8cjQaUr2MbA= 369 | go.opentelemetry.io/otel v1.12.0/go.mod h1:geaoz0L0r1BEOR81k7/n9W4TCXYCJ7bPO7K374jQHG0= 370 | go.opentelemetry.io/otel/metric v0.35.0/go.mod h1:qAcbhaTRFU6uG8QM7dDo7XvFsWcugziq/5YI065TokQ= 371 | go.opentelemetry.io/otel/sdk v1.12.0/go.mod h1:WYcvtgquYvgODEvxOry5owO2y9MyciW7JqMz6cpXShE= 372 | go.opentelemetry.io/otel/sdk/metric v0.35.0/go.mod h1:eDyp1GxSiwV98kr7w4pzrszQh/eze9MqBqPd2bCPmyE= 373 | go.opentelemetry.io/otel/trace v1.12.0/go.mod h1:pHlgBynn6s25qJ2szD+Bv+iwKJttjHSI3lUAyf0GNuQ= 374 | go.uber.org/goleak v1.2.0 h1:xqgm/S+aQvhWFTtR0XK3Jvg7z8kGV8P4X14IzwN3Eqk= 375 | go.uber.org/goleak v1.2.0/go.mod h1:XJYK+MuIchqpmGmUSAzotztawfKvYLUIgg7guXrwVUo= 376 | go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= 377 | go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= 378 | go.uber.org/zap v1.26.0 h1:sI7k6L95XOKS281NhVKOFCUNIvv9e0w4BF8N3u+tCRo= 379 | go.uber.org/zap v1.26.0/go.mod h1:dtElttAiwGvoJ/vj4IwHBS/gXsEu/pZ50mUIRWuG0so= 380 | golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= 381 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= 382 | golang.org/x/crypto v0.0.0-20190923035154-9ee001bba392/go.mod h1:/lpIB1dKB+9EgE3H3cr1v9wB50oz8l4C4h62xy7jSTY= 383 | golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= 384 | golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= 385 | golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= 386 | golang.org/x/crypto v0.1.0/go.mod h1:RecgLatLF4+eUMCP1PoPZQb+cVrJcOPbHkTkbkB9sbw= 387 | golang.org/x/crypto v0.16.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4= 388 | golang.org/x/crypto v0.31.0 h1:ihbySMvVjLAeSH1IbfcRTkD/iNscyz8rGzjF/E5hV6U= 389 | golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk= 390 | golang.org/x/exp v0.0.0-20230905200255-921286631fa9 h1:GoHiUyI/Tp2nVkLI2mCxVkOjsbSXD66ic0XW0js0R9g= 391 | golang.org/x/exp v0.0.0-20230905200255-921286631fa9/go.mod h1:S2oDrQGGwySpoQPVqRShND87VCbxmc6bL1Yd2oYrm6k= 392 | golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= 393 | golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= 394 | golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= 395 | golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= 396 | golang.org/x/mod v0.6.0-dev.0.20220106191415-9b9b3d81d5e3/go.mod h1:3p9vT2HGsQu2K1YbXdKPJLVgG5VJdoTa1poYQBtP1AY= 397 | golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= 398 | golang.org/x/mod v0.6.0/go.mod h1:4mET923SAdbXp2ki8ey+zGs1SLqsuM2Y0uvdZR/fUNI= 399 | golang.org/x/mod v0.7.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= 400 | golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= 401 | golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 402 | golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 403 | golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 404 | golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 405 | golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 406 | golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 407 | golang.org/x/net v0.0.0-20190923162816-aa69164e4478/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 408 | golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 409 | golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= 410 | golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= 411 | golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= 412 | golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= 413 | golang.org/x/net v0.0.0-20210410081132-afb366fc7cd1/go.mod h1:9tjilg8BloeKEkVJvy7fQ90B1CfIiPueXVOjqfkSzI8= 414 | golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk= 415 | golang.org/x/net v0.0.0-20211015210444-4f30a5c0130f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= 416 | golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= 417 | golang.org/x/net v0.0.0-20220425223048-2871e0cb64e4/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= 418 | golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= 419 | golang.org/x/net v0.1.0/go.mod h1:Cx3nUiGt4eDBEyega/BKRp+/AlGL8hYe7U9odMt2Cco= 420 | golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY= 421 | golang.org/x/net v0.3.0/go.mod h1:MBQ8lrhLObU/6UmLb4fmbmk5OcyYmqtbGd/9yIeKjEE= 422 | golang.org/x/net v0.5.0/go.mod h1:DivGGAXEgPSlEBzxGzZI+ZLohi+xUj054jfeKui00ws= 423 | golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= 424 | golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= 425 | golang.org/x/net v0.33.0 h1:74SYHlV8BIgHIFC/LrYkOGIwL19eTYXQ5wc6TBuO36I= 426 | golang.org/x/net v0.33.0/go.mod h1:HXLR5J+9DxmrqMwG9qjGCxZ+zKXxBru04zlTvWlWuN4= 427 | golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 428 | golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 429 | golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 430 | golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 431 | golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 432 | golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 433 | golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 434 | golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 435 | golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 436 | golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y= 437 | golang.org/x/sync v0.10.0 h1:3NQrjDixjgGwUOCaF8w2+VYHv0Ve/vGYSbdkTa98gmQ= 438 | golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= 439 | golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 440 | golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 441 | golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 442 | golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 443 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 444 | golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 445 | golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 446 | golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 447 | golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 448 | golang.org/x/sys v0.0.0-20190922100055-0a153f010e69/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 449 | golang.org/x/sys v0.0.0-20190924154521-2837fb4f24fe/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 450 | golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 451 | golang.org/x/sys v0.0.0-20191010194322-b09406accb47/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 452 | golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 453 | golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 454 | golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 455 | golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 456 | golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 457 | golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 458 | golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 459 | golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 460 | golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 461 | golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 462 | golang.org/x/sys v0.0.0-20210303074136-134d130e1a04/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 463 | golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 464 | golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 465 | golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 466 | golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 467 | golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 468 | golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 469 | golang.org/x/sys v0.0.0-20211019181941-9d821ace8654/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 470 | golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 471 | golang.org/x/sys v0.0.0-20220319134239-a9b59b0215f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 472 | golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 473 | golang.org/x/sys v0.0.0-20220422013727-9388b58f7150/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 474 | golang.org/x/sys v0.0.0-20220503163025-988cb79eb6c6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 475 | golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 476 | golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 477 | golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 478 | golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 479 | golang.org/x/sys v0.0.0-20220919091848-fb04ddd9f9c8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 480 | golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 481 | golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 482 | golang.org/x/sys v0.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 483 | golang.org/x/sys v0.4.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 484 | golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 485 | golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 486 | golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 487 | golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= 488 | golang.org/x/sys v0.28.0 h1:Fksou7UEQUWlKvIdsqzJmUmCX3cZuD2+P3XyyzwMhlA= 489 | golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= 490 | golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= 491 | golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= 492 | golang.org/x/term v0.1.0/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= 493 | golang.org/x/term v0.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc= 494 | golang.org/x/term v0.3.0/go.mod h1:q750SLmJuPmVoN1blW3UFBPREJfb1KmY3vwxfr+nFDA= 495 | golang.org/x/term v0.4.0/go.mod h1:9P2UbLfCdcvo3p/nzKvsmas4TnlujnuoV9hGgYzW1lQ= 496 | golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= 497 | golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo= 498 | golang.org/x/term v0.15.0/go.mod h1:BDl952bC7+uMoWR75FIrCDx79TPU9oHkTZ9yRbYOrX0= 499 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 500 | golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= 501 | golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 502 | golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 503 | golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= 504 | golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= 505 | golang.org/x/text v0.5.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= 506 | golang.org/x/text v0.6.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= 507 | golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= 508 | golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= 509 | golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= 510 | golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo= 511 | golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ= 512 | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 513 | golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= 514 | golang.org/x/tools v0.0.0-20190907020128-2ca718005c18/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 515 | golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 516 | golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= 517 | golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= 518 | golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= 519 | golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= 520 | golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= 521 | golang.org/x/tools v0.1.10/go.mod h1:Uh6Zz+xoGYZom868N8YTex3t7RhtHDBrE8Gzo9bV56E= 522 | golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= 523 | golang.org/x/tools v0.2.0/go.mod h1:y4OqIKeOV/fWJetJ8bXPU1sEVniLMIyDAZWeHdV+NTA= 524 | golang.org/x/tools v0.4.0/go.mod h1:UE5sM2OK9E/d67R0ANs2xJizIymRP5gJU295PvKXxjQ= 525 | golang.org/x/tools v0.5.0/go.mod h1:N+Kgy78s5I24c24dU8OfWNEotWjutIs8SnJvn5IDq+k= 526 | golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= 527 | golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 528 | golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 529 | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 530 | golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 531 | google.golang.org/genproto v0.0.0-20230913181813-007df8e322eb h1:XFBgcDwm7irdHTbz4Zk2h7Mh+eis4nfJEFQFYzJzuIA= 532 | google.golang.org/genproto v0.0.0-20230913181813-007df8e322eb/go.mod h1:yZTlhN0tQnXo3h00fuXNCxJdLdIdnVFVBaRJ5LWBbw4= 533 | google.golang.org/genproto/googleapis/api v0.0.0-20230913181813-007df8e322eb h1:lK0oleSc7IQsUxO3U5TjL9DWlsxpEBemh+zpB7IqhWI= 534 | google.golang.org/genproto/googleapis/api v0.0.0-20230913181813-007df8e322eb/go.mod h1:KjSP20unUpOx5kyQUFa7k4OJg0qeJ7DEZflGDu2p6Bk= 535 | google.golang.org/genproto/googleapis/rpc v0.0.0-20230913181813-007df8e322eb h1:Isk1sSH7bovx8Rti2wZK0UZF6oraBDK74uoyLEEVFN0= 536 | google.golang.org/genproto/googleapis/rpc v0.0.0-20230913181813-007df8e322eb/go.mod h1:+Bk1OCOj40wS2hwAMA+aCW9ypzm63QTBBHp6lQ3p+9M= 537 | google.golang.org/grpc v1.58.3 h1:BjnpXut1btbtgN/6sp+brB2Kbm2LjNXnidYujAVbSoQ= 538 | google.golang.org/grpc v1.58.3/go.mod h1:tgX3ZQDlNJGU96V6yHh1T/JeoBQ2TXdr43YbYSsCJk0= 539 | google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= 540 | google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= 541 | google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= 542 | google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= 543 | google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= 544 | google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= 545 | google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= 546 | google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= 547 | google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= 548 | google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI= 549 | google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= 550 | gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= 551 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 552 | gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 553 | gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 554 | gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= 555 | gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= 556 | gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 557 | gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 558 | gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 559 | gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 560 | gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 561 | gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 562 | gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= 563 | gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= 564 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 565 | gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= 566 | gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 567 | --------------------------------------------------------------------------------