From da62b8f230ace80f71383e3dbd4f02e8b7b21a1f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=8E=8B=E6=80=A7=E9=A9=8A?= Date: Sat, 8 Nov 2025 14:37:41 +0800 Subject: [PATCH] feat: add verify member --- etc/gateway.yaml | 22 +- generate/api/member.api | 5 +- go.mod | 82 ++++--- go.sum | 206 ++++++------------ internal/config/config.go | 19 ++ internal/domain/const.go | 3 + .../handler/user/get_user_info_handler.go | 7 +- .../user/request_verification_code_handler.go | 9 +- .../user/submit_verification_code_handler.go | 9 +- .../handler/user/update_password_handler.go | 9 +- .../handler/user/update_user_info_handler.go | 7 +- internal/logic/auth/register_logic.go | 5 +- .../auth/request_password_reset_logic.go | 81 ++++++- internal/logic/ping/ping_logic.go | 5 +- .../user/request_verification_code_logic.go | 184 +++++++++++++++- .../user/submit_verification_code_logic.go | 2 +- internal/logic/user/update_user_info_logic.go | 114 +++++++++- internal/middleware/auth_middleware.go | 33 +-- internal/svc/notification_model.go | 71 ++++++ internal/svc/service_context.go | 6 +- internal/types/types.go | 5 +- internal/utils/email_template/const.go | 32 +++ internal/utils/email_template/get.go | 21 ++ internal/utils/email_template/template.go | 150 +++++++++++++ pkg/notification/domain/error.go | 1 + pkg/notification/domain/repository/mail.go | 9 +- pkg/notification/domain/usecase/delivery.go | 9 +- pkg/notification/repository/smtp_mailer.go | 8 +- pkg/notification/usecase/delivery.go | 9 +- 29 files changed, 852 insertions(+), 271 deletions(-) create mode 100644 internal/domain/const.go create mode 100644 internal/svc/notification_model.go create mode 100644 internal/utils/email_template/const.go create mode 100644 internal/utils/email_template/get.go create mode 100644 internal/utils/email_template/template.go create mode 100644 pkg/notification/domain/error.go diff --git a/etc/gateway.yaml b/etc/gateway.yaml index f09c57d..5020996 100644 --- a/etc/gateway.yaml +++ b/etc/gateway.yaml @@ -58,4 +58,24 @@ RoleConfig: UIDLength: 6 AdminRoleUID: "AM000000" AdminUserUID: "B000000" - DefaultRoleName: "USER" \ No newline at end of file + DefaultRoleName: "USER" + + +SMTPConfig: + Enable: true + GoroutinePoolNum: 1000 + Host: smtp.mailgun.org + Port: 465 + Username: postmaster@code.30cm.net + Password: 595da25c2a44ef2629ba92bf88ae94f1-02300200-af1d3b04 + Sender: daniel.wang@code.30cm.net + SenderName: "Digimon 平台" + + +DeliveryConfig: + max_retries : 5 + initial_delay : 500ms + backoff_factor: 2.0 + max_delay : 5000ms + Timeout: 1000ms + enable_history: false diff --git a/generate/api/member.api b/generate/api/member.api index 1ce53bb..a4aa893 100644 --- a/generate/api/member.api +++ b/generate/api/member.api @@ -94,13 +94,9 @@ type ( UserStatus string `json:"user_status"` // 用戶狀態 PreferredLanguage string `json:"preferred_language"` // 偏好語言 Currency string `json:"currency"` // 偏好幣種 - National string `json:"national"` // 國家 - PostCode string `json:"post_code"` // 郵遞區號 - Carrier string `json:"carrier"` // 載具 Role string `json:"role"` // 角色 UpdateAt string `json:"update_at"` CreateAt string `json:"create_at"` - Authorization } // UpdateUserInfoReq 更新會員資訊的請求結構 @@ -131,6 +127,7 @@ type ( // RequestVerificationCodeReq 請求發送驗證碼 RequestVerificationCodeReq { // 驗證目的:'email_verification' 或 'phone_verification' + Account string `json:"account" validate:"required` Purpose string `json:"purpose" validate:"required,oneof=email_verification phone_verification"` Authorization } diff --git a/go.mod b/go.mod index 570e0cf..c7f15c6 100644 --- a/go.mod +++ b/go.mod @@ -28,21 +28,19 @@ require ( require ( dario.cat/mergo v1.0.2 // indirect - github.com/Azure/go-ansiterm v0.0.0-20250102033503-faa5f7b0171c // indirect - github.com/Masterminds/goutils v1.1.1 // indirect - github.com/Masterminds/semver v1.5.0 // indirect - github.com/Masterminds/sprig v2.22.0+incompatible // indirect + github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 // indirect + github.com/Masterminds/semver v1.4.2 // indirect + github.com/Masterminds/sprig v2.16.0+incompatible // indirect github.com/Microsoft/go-winio v0.6.2 // indirect - github.com/PuerkitoBio/goquery v1.10.3 // indirect - github.com/andybalholm/cascadia v1.3.3 // indirect + github.com/PuerkitoBio/goquery v1.5.0 // indirect + github.com/andybalholm/cascadia v1.0.0 // indirect + github.com/aokoli/goutils v1.0.1 // indirect github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.13 // indirect github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.13 // indirect github.com/aws/smithy-go v1.23.2 // indirect github.com/beorn7/perks v1.0.1 // indirect github.com/cenkalti/backoff/v4 v4.3.0 // indirect github.com/cespare/xxhash/v2 v2.3.0 // indirect - github.com/clipperhouse/stringish v0.1.1 // indirect - github.com/clipperhouse/uax29/v2 v2.3.0 // indirect github.com/containerd/errdefs v1.0.0 // indirect github.com/containerd/errdefs/pkg v0.3.0 // indirect github.com/containerd/log v0.1.0 // indirect @@ -51,97 +49,93 @@ require ( github.com/davecgh/go-spew v1.1.1 // indirect github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect github.com/distribution/reference v0.6.0 // indirect - github.com/docker/docker v28.5.2+incompatible // indirect + github.com/docker/docker v28.5.1+incompatible // indirect github.com/docker/go-connections v0.6.0 // indirect github.com/docker/go-units v0.5.0 // indirect - github.com/ebitengine/purego v0.9.1 // indirect + github.com/ebitengine/purego v0.8.4 // indirect github.com/fatih/color v1.18.0 // indirect github.com/felixge/httpsnoop v1.0.4 // indirect - github.com/gabriel-vasile/mimetype v1.4.11 // indirect + github.com/gabriel-vasile/mimetype v1.4.10 // indirect github.com/go-logr/logr v1.4.3 // indirect github.com/go-logr/stdr v1.2.2 // indirect - github.com/go-ole/go-ole v1.3.0 // indirect + github.com/go-ole/go-ole v1.2.6 // indirect github.com/go-playground/locales v0.14.1 // indirect github.com/go-playground/universal-translator v0.18.1 // indirect github.com/golang/snappy v1.0.0 // indirect github.com/google/uuid v1.6.0 // indirect - github.com/gorilla/css v1.0.1 // indirect + github.com/gorilla/css v1.0.0 // indirect github.com/grafana/pyroscope-go v1.2.7 // indirect github.com/grafana/pyroscope-go/godeltaprof v0.1.9 // indirect github.com/grpc-ecosystem/grpc-gateway/v2 v2.20.0 // indirect - github.com/huandu/xstrings v1.5.0 // indirect - github.com/imdario/mergo v0.3.16 // indirect - github.com/jaytaylor/html2text v0.0.0-20230321000545-74c2419ad056 // indirect - github.com/klauspost/compress v1.18.1 // indirect + github.com/huandu/xstrings v1.2.0 // indirect + github.com/imdario/mergo v0.3.6 // indirect + github.com/jaytaylor/html2text v0.0.0-20180606194806-57d518f124b0 // indirect + github.com/klauspost/compress v1.18.0 // indirect github.com/leodido/go-urn v1.4.0 // indirect - github.com/lufia/plan9stats v0.0.0-20251013123823-9fd1530e3ec3 // indirect + github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 // indirect github.com/magiconair/properties v1.8.10 // indirect - github.com/mattn/go-colorable v0.1.14 // indirect + github.com/mattn/go-colorable v0.1.13 // indirect github.com/mattn/go-isatty v0.0.20 // indirect - github.com/mattn/go-runewidth v0.0.19 // indirect - github.com/mitchellh/copystructure v1.2.0 // indirect - github.com/mitchellh/reflectwalk v1.0.2 // indirect + github.com/mattn/go-runewidth v0.0.15 // indirect github.com/moby/docker-image-spec v1.3.1 // indirect github.com/moby/go-archive v0.1.0 // indirect github.com/moby/patternmatcher v0.6.0 // indirect github.com/moby/sys/sequential v0.6.0 // indirect github.com/moby/sys/user v0.4.0 // indirect github.com/moby/sys/userns v0.1.0 // indirect - github.com/moby/term v0.5.2 // indirect + github.com/moby/term v0.5.0 // indirect github.com/morikuni/aec v1.0.0 // indirect github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect - github.com/olekukonko/cat v0.0.0-20250911104152-50322a0618f6 // indirect - github.com/olekukonko/errors v1.1.0 // indirect - github.com/olekukonko/ll v0.1.2 // indirect - github.com/olekukonko/tablewriter v1.1.0 // indirect + github.com/olekukonko/tablewriter v0.0.1 // indirect github.com/opencontainers/go-digest v1.0.0 // indirect github.com/opencontainers/image-spec v1.1.1 // indirect github.com/openzipkin/zipkin-go v0.4.3 // indirect github.com/pelletier/go-toml/v2 v2.2.2 // indirect github.com/pkg/errors v0.9.1 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect - github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55 // indirect + github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c // indirect github.com/prometheus/client_golang v1.21.1 // indirect github.com/prometheus/client_model v0.6.1 // indirect github.com/prometheus/common v0.62.0 // indirect github.com/prometheus/procfs v0.15.1 // indirect - github.com/redis/go-redis/v9 v9.16.0 // indirect - github.com/rivo/uniseg v0.4.7 // indirect - github.com/russross/blackfriday/v2 v2.1.0 // indirect - github.com/shirou/gopsutil/v4 v4.25.10 // indirect + github.com/redis/go-redis/v9 v9.14.0 // indirect + github.com/rivo/uniseg v0.2.0 // indirect + github.com/russross/blackfriday/v2 v2.0.1 // indirect + github.com/shirou/gopsutil/v4 v4.25.6 // indirect + github.com/shurcooL/sanitized_anchor_name v1.0.0 // indirect github.com/sirupsen/logrus v1.9.3 // indirect github.com/spaolacci/murmur3 v1.1.0 // indirect github.com/ssor/bom v0.0.0-20170718123548-6386211fdfcf // indirect - github.com/tklauser/go-sysconf v0.3.15 // indirect - github.com/tklauser/numcpus v0.10.0 // indirect - github.com/vanng822/css v1.0.1 // indirect - github.com/vanng822/go-premailer v1.25.0 // indirect + github.com/tklauser/go-sysconf v0.3.12 // indirect + github.com/tklauser/numcpus v0.6.1 // indirect + github.com/vanng822/css v0.0.0-20190504095207-a21e860bcd04 // indirect + github.com/vanng822/go-premailer v0.0.0-20191214114701-be27abe028fe // indirect github.com/xdg-go/pbkdf2 v1.0.0 // indirect github.com/xdg-go/scram v1.1.2 // indirect github.com/xdg-go/stringprep v1.0.4 // indirect github.com/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78 // indirect github.com/yuin/gopher-lua v1.1.1 // indirect github.com/yusufpapurcu/wmi v1.2.4 // indirect - go.opentelemetry.io/auto/sdk v1.2.1 // indirect - go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.63.0 // indirect - go.opentelemetry.io/otel v1.38.0 // indirect + go.opentelemetry.io/auto/sdk v1.1.0 // indirect + go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.49.0 // indirect + go.opentelemetry.io/otel v1.37.0 // indirect go.opentelemetry.io/otel/exporters/jaeger v1.17.0 // indirect go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.24.0 // indirect go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.24.0 // indirect go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.24.0 // indirect go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.24.0 // indirect go.opentelemetry.io/otel/exporters/zipkin v1.24.0 // indirect - go.opentelemetry.io/otel/metric v1.38.0 // indirect - go.opentelemetry.io/otel/sdk v1.38.0 // indirect - go.opentelemetry.io/otel/trace v1.38.0 // indirect + go.opentelemetry.io/otel/metric v1.37.0 // indirect + go.opentelemetry.io/otel/sdk v1.37.0 // indirect + go.opentelemetry.io/otel/trace v1.37.0 // indirect go.opentelemetry.io/proto/otlp v1.3.1 // indirect go.uber.org/automaxprocs v1.6.0 // indirect - golang.org/x/net v0.46.0 // indirect + golang.org/x/net v0.45.0 // indirect golang.org/x/sync v0.17.0 // indirect golang.org/x/sys v0.37.0 // indirect golang.org/x/text v0.30.0 // indirect google.golang.org/genproto/googleapis/api v0.0.0-20250804133106-a7a43d27e69b // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20251103181224-f26f9409b101 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20250804133106-a7a43d27e69b // indirect gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc // indirect gopkg.in/yaml.v2 v2.4.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect diff --git a/go.sum b/go.sum index 8edf7cd..4d1a028 100644 --- a/go.sum +++ b/go.sum @@ -4,26 +4,21 @@ dario.cat/mergo v1.0.2 h1:85+piFYR1tMbRrLcDwR18y4UKJ3aH1Tbzi24VRW1TK8= dario.cat/mergo v1.0.2/go.mod h1:E/hbnu0NxMFBjpMIE34DRGLWqDy0g5FuKDhCb31ngxA= github.com/AdaLogics/go-fuzz-headers v0.0.0-20240806141605-e8a1dd7889d6 h1:He8afgbRMd7mFxO99hRNu+6tazq8nFF9lIwo9JFroBk= github.com/AdaLogics/go-fuzz-headers v0.0.0-20240806141605-e8a1dd7889d6/go.mod h1:8o94RPi1/7XTJvwPpRSzSUedZrtlirdB3r9Z20bi2f8= -github.com/Azure/go-ansiterm v0.0.0-20250102033503-faa5f7b0171c h1:udKWzYgxTojEKWjV8V+WSxDXJ4NFATAsZjh8iIbsQIg= -github.com/Azure/go-ansiterm v0.0.0-20250102033503-faa5f7b0171c/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E= -github.com/Masterminds/goutils v1.1.1 h1:5nUrii3FMTL5diU80unEVvNevw1nH4+ZV4DSLVJLSYI= -github.com/Masterminds/goutils v1.1.1/go.mod h1:8cTjp+g8YejhMuvIA5y2vz3BpJxksy863GQaJW2MFNU= +github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 h1:UQHMgLO+TxOElx5B5HZ4hJQsoJ/PvUvKRhJHDQXO8P8= +github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E= +github.com/Masterminds/semver v1.4.2 h1:WBLTQ37jOCzSLtXNdoo8bNM8876KhNqOKvrlGITgsTc= github.com/Masterminds/semver v1.4.2/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF078ddwwvV3Y= -github.com/Masterminds/semver v1.5.0 h1:H65muMkzWKEuNDnfl9d70GUjFniHKHRbFPGBuZ3QEww= -github.com/Masterminds/semver v1.5.0/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF078ddwwvV3Y= +github.com/Masterminds/sprig v2.16.0+incompatible h1:QZbMUPxRQ50EKAq3LFMnxddMu88/EUUG3qmxwtDmPsY= github.com/Masterminds/sprig v2.16.0+incompatible/go.mod h1:y6hNFY5UBTIWBxnzTeuNhlNS5hqE0NB0E6fgfo2Br3o= -github.com/Masterminds/sprig v2.22.0+incompatible h1:z4yfnGrZ7netVz+0EDJ0Wi+5VZCSYp4Z0m2dk6cEM60= -github.com/Masterminds/sprig v2.22.0+incompatible/go.mod h1:y6hNFY5UBTIWBxnzTeuNhlNS5hqE0NB0E6fgfo2Br3o= github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY= github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU= +github.com/PuerkitoBio/goquery v1.5.0 h1:uGvmFXOA73IKluu/F84Xd1tt/z07GYm8X49XKHP7EJk= github.com/PuerkitoBio/goquery v1.5.0/go.mod h1:qD2PgZ9lccMbQlc7eEOjaeRlFQON7xY8kdmcsrnKqMg= -github.com/PuerkitoBio/goquery v1.10.3 h1:pFYcNSqHxBD06Fpj/KsbStFRsgRATgnf3LeXiUkhzPo= -github.com/PuerkitoBio/goquery v1.10.3/go.mod h1:tMUX0zDMHXYlAQk6p35XxQMqMweEKB7iK7iLNd4RH4Y= github.com/alicebob/miniredis/v2 v2.35.0 h1:QwLphYqCEAo1eu1TqPRN2jgVMPBweeQcR21jeqDCONI= github.com/alicebob/miniredis/v2 v2.35.0/go.mod h1:TcL7YfarKPGDAthEtl5NBeHZfeUQj6OXMm/+iu5cLMM= +github.com/andybalholm/cascadia v1.0.0 h1:hOCXnnZ5A+3eVDX8pvgl4kofXv2ELss0bKcqRySc45o= github.com/andybalholm/cascadia v1.0.0/go.mod h1:GsXiBklL0woXo1j/WYWtSYYC4ouU9PqHO0sqidkEA4Y= -github.com/andybalholm/cascadia v1.3.3 h1:AG2YHrzJIm4BZ19iwJ/DAua6Btl3IwJX+VI4kktS1LM= -github.com/andybalholm/cascadia v1.3.3/go.mod h1:xNd9bqTn98Ln4DwST8/nG+H0yuB8Hmgu1YHNnWw0GeA= +github.com/aokoli/goutils v1.0.1 h1:7fpzNGoJ3VA8qcrm++XEE1QUe0mIwNeLa02Nwq7RDkg= github.com/aokoli/goutils v1.0.1/go.mod h1:SijmP0QR8LtwsmDs8Yii5Z/S4trXFGFC2oO5g9DP+DQ= github.com/aws/aws-sdk-go-v2 v1.39.6 h1:2JrPCVgWJm7bm83BDwY5z8ietmeJUbh3O2ACnn+Xsqk= github.com/aws/aws-sdk-go-v2 v1.39.6/go.mod h1:c9pm7VwuW0UPxAEYGyTmyurVcNrbF6Rt/wixFqDhcjE= @@ -47,10 +42,6 @@ github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK3 github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE= github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= -github.com/clipperhouse/stringish v0.1.1 h1:+NSqMOr3GR6k1FdRhhnXrLfztGzuG+VuFDfatpWHKCs= -github.com/clipperhouse/stringish v0.1.1/go.mod h1:v/WhFtE1q0ovMta2+m+UbpZ+2/HEXNWYXQgCt4hdOzA= -github.com/clipperhouse/uax29/v2 v2.3.0 h1:SNdx9DVUqMoBuBoW3iLOj4FQv3dN5mDtuqwuhIGpJy4= -github.com/clipperhouse/uax29/v2 v2.3.0/go.mod h1:Wn1g7MK6OoeDT0vL+Q0SQLDz/KpfsVRgg6W7ihQeh4g= github.com/containerd/errdefs v1.0.0 h1:tg5yIfIlQIrxYtu9ajqY42W3lpS19XqdxRQeEwYG8PI= github.com/containerd/errdefs v1.0.0/go.mod h1:+YBYIdtsnF4Iw6nWZhJcqGSg/dwvV7tyJ/kCkyJ2k+M= github.com/containerd/errdefs/pkg v0.3.0 h1:9IKJ06FvyNlexW690DXuQNx2KA2cUJXx151Xdx3ZPPE= @@ -70,29 +61,28 @@ github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/r github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc= github.com/distribution/reference v0.6.0 h1:0IXCQ5g4/QMHHkarYzh5l+u8T3t73zM5QvfrDyIgxBk= github.com/distribution/reference v0.6.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E= -github.com/docker/docker v28.5.2+incompatible h1:DBX0Y0zAjZbSrm1uzOkdr1onVghKaftjlSWt4AFexzM= -github.com/docker/docker v28.5.2+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= +github.com/docker/docker v28.5.1+incompatible h1:Bm8DchhSD2J6PsFzxC35TZo4TLGR2PdW/E69rU45NhM= +github.com/docker/docker v28.5.1+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= github.com/docker/go-connections v0.6.0 h1:LlMG9azAe1TqfR7sO+NJttz1gy6KO7VJBh+pMmjSD94= github.com/docker/go-connections v0.6.0/go.mod h1:AahvXYshr6JgfUJGdDCs2b5EZG/vmaMAntpSFH5BFKE= github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4= github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= -github.com/ebitengine/purego v0.9.1 h1:a/k2f2HQU3Pi399RPW1MOaZyhKJL9w/xFpKAg4q1s0A= -github.com/ebitengine/purego v0.9.1/go.mod h1:iIjxzd6CiRiOG0UyXP+V1+jWqUXVjPKLAI0mRfJZTmQ= +github.com/ebitengine/purego v0.8.4 h1:CF7LEKg5FFOsASUj0+QwaXf8Ht6TlFxg09+S9wz0omw= +github.com/ebitengine/purego v0.8.4/go.mod h1:iIjxzd6CiRiOG0UyXP+V1+jWqUXVjPKLAI0mRfJZTmQ= github.com/fatih/color v1.18.0 h1:S8gINlzdQ840/4pfAwic/ZE0djQEH3wM94VfqLTZcOM= github.com/fatih/color v1.18.0/go.mod h1:4FelSpRwEGDpQ12mAdzqdOukCy4u8WUtOY6lkT/6HfU= github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= -github.com/gabriel-vasile/mimetype v1.4.11 h1:AQvxbp830wPhHTqc1u7nzoLT+ZFxGY7emj5DR5DYFik= -github.com/gabriel-vasile/mimetype v1.4.11/go.mod h1:d+9Oxyo1wTzWdyVUPMmXFvp4F9tea18J8ufA774AB3s= +github.com/gabriel-vasile/mimetype v1.4.10 h1:zyueNbySn/z8mJZHLt6IPw0KoZsiQNszIpU+bX4+ZK0= +github.com/gabriel-vasile/mimetype v1.4.10/go.mod h1:d+9Oxyo1wTzWdyVUPMmXFvp4F9tea18J8ufA774AB3s= github.com/go-gomail/gomail v0.0.0-20160411212932-81ebce5c23df/go.mod h1:GJr+FCSXshIwgHBtLglIg9M2l2kQSi6QjVAngtzI08Y= github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI= github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= +github.com/go-ole/go-ole v1.2.6 h1:/Fpf6oFPoeFik9ty7siob0G6Ke8QvQEuVcuChpwXzpY= github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0= -github.com/go-ole/go-ole v1.3.0 h1:Dt6ye7+vXGIKZ7Xtk4s6/xVdGDQynvom7xCFEdWr6uE= -github.com/go-ole/go-ole v1.3.0/go.mod h1:5LS6F96DhAwUc7C+1HLexzMXY1xGRSryjyPPKW6zv78= github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s= github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA= @@ -107,15 +97,14 @@ github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= github.com/golang/snappy v1.0.0 h1:Oy607GVXHs7RtbggtPBnr2RmDArIsAefDwvrdWvRhGs= github.com/golang/snappy v1.0.0/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= -github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= github.com/google/uuid v1.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/gorilla/css v1.0.0 h1:BQqNyPTi50JCFMTw/b67hByjMVXZRwGha6wxVGkeihY= github.com/gorilla/css v1.0.0/go.mod h1:Dn721qIggHpt4+EFCcTLTU/vk5ySda2ReITrtgBl60c= -github.com/gorilla/css v1.0.1 h1:ntNaBIghp6JmvWnxbZKANoLyuXTPZ4cAMlo6RyhlbO8= -github.com/gorilla/css v1.0.1/go.mod h1:BvnYkspnSzMmwRK+b8/xgNPLiIuNZr6vbZBTPQ2A3b0= github.com/grafana/pyroscope-go v1.2.7 h1:VWBBlqxjyR0Cwk2W6UrE8CdcdD80GOFNutj0Kb1T8ac= github.com/grafana/pyroscope-go v1.2.7/go.mod h1:o/bpSLiJYYP6HQtvcoVKiE9s5RiNgjYTj1DhiddP2Pc= github.com/grafana/pyroscope-go/godeltaprof v0.1.9 h1:c1Us8i6eSmkW+Ez05d3co8kasnuOY813tbMN8i/a3Og= @@ -124,17 +113,14 @@ github.com/grpc-ecosystem/grpc-gateway/v2 v2.20.0 h1:bkypFPDjIYGfCYD5mRBvpqxfYX1 github.com/grpc-ecosystem/grpc-gateway/v2 v2.20.0/go.mod h1:P+Lt/0by1T8bfcF3z737NnSbmxQAppXMRziHUxPOC8k= github.com/h2non/parth v0.0.0-20190131123155-b4df798d6542 h1:2VTzZjLZBgl62/EtslCrtky5vbi9dd7HrQPQIx6wqiw= github.com/h2non/parth v0.0.0-20190131123155-b4df798d6542/go.mod h1:Ow0tF8D4Kplbc8s8sSb3V2oUCygFHVp8gC3Dn6U4MNI= +github.com/huandu/xstrings v1.2.0 h1:yPeWdRnmynF7p+lLYz0H2tthW9lqhMJrQV/U7yy4wX0= github.com/huandu/xstrings v1.2.0/go.mod h1:DvyZB1rfVYsBIigL8HwpZgxHwXozlTgGqn63UyNX5k4= -github.com/huandu/xstrings v1.5.0 h1:2ag3IFq9ZDANvthTwTiqSSZLjDc+BedvHPAp5tJy2TI= -github.com/huandu/xstrings v1.5.0/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE= +github.com/imdario/mergo v0.3.6 h1:xTNEAn+kxVO7dTZGu0CegyqKZmoWFI0rF8UxjlB2d28= github.com/imdario/mergo v0.3.6/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA= -github.com/imdario/mergo v0.3.16 h1:wwQJbIsHYGMUyLSPrEq1CT16AhnhNJQ51+4fdHUnCl4= -github.com/imdario/mergo v0.3.16/go.mod h1:WBLT9ZmE3lPoWsEzCh9LPo3TiwVN+ZKEjmz+hD27ysY= +github.com/jaytaylor/html2text v0.0.0-20180606194806-57d518f124b0 h1:xqgexXAGQgY3HAjNPSaCqn5Aahbo5TKsmhp8VRfr1iQ= github.com/jaytaylor/html2text v0.0.0-20180606194806-57d518f124b0/go.mod h1:CVKlgaMiht+LXvHG173ujK6JUhZXKb2u/BQtjPDIvyk= -github.com/jaytaylor/html2text v0.0.0-20230321000545-74c2419ad056 h1:iCHtR9CQyktQ5+f3dMVZfwD2KWJUgm7M0gdL9NGr8KA= -github.com/jaytaylor/html2text v0.0.0-20230321000545-74c2419ad056/go.mod h1:CVKlgaMiht+LXvHG173ujK6JUhZXKb2u/BQtjPDIvyk= -github.com/klauspost/compress v1.18.1 h1:bcSGx7UbpBqMChDtsF28Lw6v/G94LPrrbMbdC3JH2co= -github.com/klauspost/compress v1.18.1/go.mod h1:ZQFFVG+MdnR0P+l6wpXgIL4NTtwiKIdBnrBd8Nrxr+0= +github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo= +github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= @@ -146,25 +132,22 @@ github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0 github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ= github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI= -github.com/lufia/plan9stats v0.0.0-20251013123823-9fd1530e3ec3 h1:PwQumkgq4/acIiZhtifTV5OUqqiP82UAl0h87xj/l9k= -github.com/lufia/plan9stats v0.0.0-20251013123823-9fd1530e3ec3/go.mod h1:autxFIvghDt3jPTLoqZ9OZ7s9qTGNAWmYCjVFWPX/zg= +github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 h1:6E+4a0GO5zZEnZ81pIr0yLvtUWk2if982qA3F3QD6H4= +github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0/go.mod h1:zJYVVT2jmtg6P3p1VtQj7WsuWi/y4VnjVBn7F8KPB3I= github.com/magiconair/properties v1.8.10 h1:s31yESBquKXCV9a/ScB3ESkOjUYYv+X0rg8SYxI99mE= github.com/magiconair/properties v1.8.10/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0= github.com/matcornic/hermes/v2 v2.1.0 h1:9TDYFBPFv6mcXanaDmRDEp/RTWj0dTTi+LpFnnnfNWc= github.com/matcornic/hermes/v2 v2.1.0/go.mod h1:2+ziJeoyRfaLiATIL8VZ7f9hpzH4oDHqTmn0bhrsgVI= -github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE= -github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8= +github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= +github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= +github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/mattn/go-runewidth v0.0.3/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= -github.com/mattn/go-runewidth v0.0.19 h1:v++JhqYnZuu5jSKrk9RbgF5v4CGUjqRfBm05byFGLdw= -github.com/mattn/go-runewidth v0.0.19/go.mod h1:XBkDxAl56ILZc9knddidhrOlY5R/pDhgLpndooCuJAs= +github.com/mattn/go-runewidth v0.0.15 h1:UNAjwbU9l54TA3KzvqLGxwWjHmMgBUVhBiTjelZgg3U= +github.com/mattn/go-runewidth v0.0.15/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= github.com/minchao/go-mitake v1.0.0 h1:OgfCUkSRftd6sWibpJyeKU3/gPQhq1t0ttHsnoaeVgQ= github.com/minchao/go-mitake v1.0.0/go.mod h1:RAo0TijPUqhM2ZLMqP9x76wsomL11Ud4sDSwRYwbeGU= -github.com/mitchellh/copystructure v1.2.0 h1:vpKXTN4ewci03Vljg/q9QvCGUDttBOGBIa15WveJJGw= -github.com/mitchellh/copystructure v1.2.0/go.mod h1:qLl+cE2AmVv+CoeAwDPye/v+N2HKCj9FbZEVFJRxO9s= -github.com/mitchellh/reflectwalk v1.0.2 h1:G2LzWKi524PWgd3mLHV8Y5k7s6XUvT0Gef6zxSIeXaQ= -github.com/mitchellh/reflectwalk v1.0.2/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= github.com/moby/docker-image-spec v1.3.1 h1:jMKff3w6PgbfSa69GfNg+zN/XLhfXJGnEx3Nl2EsFP0= github.com/moby/docker-image-spec v1.3.1/go.mod h1:eKmb5VW8vQEh/BAr2yvVNvuiJuY6UIocYsFu/DxxRpo= github.com/moby/go-archive v0.1.0 h1:Kk/5rdW/g+H8NHdJW2gsXyZ7UnzvJNOy6VKJqueWdcQ= @@ -179,21 +162,14 @@ github.com/moby/sys/user v0.4.0 h1:jhcMKit7SA80hivmFJcbB1vqmw//wU61Zdui2eQXuMs= github.com/moby/sys/user v0.4.0/go.mod h1:bG+tYYYJgaMtRKgEmuueC0hJEAZWwtIbZTB+85uoHjs= github.com/moby/sys/userns v0.1.0 h1:tVLXkFOxVu9A64/yh59slHVv9ahO9UIev4JZusOLG/g= github.com/moby/sys/userns v0.1.0/go.mod h1:IHUYgu/kao6N8YZlp9Cf444ySSvCmDlmzUcYfDHOl28= -github.com/moby/term v0.5.2 h1:6qk3FJAFDs6i/q3W/pQ97SX192qKfZgGjCQqfCJkgzQ= -github.com/moby/term v0.5.2/go.mod h1:d3djjFCrjnB+fl8NJux+EJzu0msscUP+f8it8hPkFLc= +github.com/moby/term v0.5.0 h1:xt8Q1nalod/v7BqbG21f8mQPqH+xAaC9C3N3wfWbVP0= +github.com/moby/term v0.5.0/go.mod h1:8FzsFHVUBGZdbDsJw/ot+X+d5HLUbvklYLJ9uGfcI3Y= github.com/morikuni/aec v1.0.0 h1:nP9CBfwrvYnBRgY6qfDQkygYDmYwOilePFkwzv4dU8A= github.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= -github.com/olekukonko/cat v0.0.0-20250911104152-50322a0618f6 h1:zrbMGy9YXpIeTnGj4EljqMiZsIcE09mmF8XsD5AYOJc= -github.com/olekukonko/cat v0.0.0-20250911104152-50322a0618f6/go.mod h1:rEKTHC9roVVicUIfZK7DYrdIoM0EOr8mK1Hj5s3JjH0= -github.com/olekukonko/errors v1.1.0 h1:RNuGIh15QdDenh+hNvKrJkmxxjV4hcS50Db478Ou5sM= -github.com/olekukonko/errors v1.1.0/go.mod h1:ppzxA5jBKcO1vIpCXQ9ZqgDh8iwODz6OXIGKU8r5m4Y= -github.com/olekukonko/ll v0.1.2 h1:lkg/k/9mlsy0SxO5aC+WEpbdT5K83ddnNhAepz7TQc0= -github.com/olekukonko/ll v0.1.2/go.mod h1:b52bVQRRPObe+yyBl0TxNfhesL0nedD4Cht0/zx55Ew= +github.com/olekukonko/tablewriter v0.0.1 h1:b3iUnf1v+ppJiOfNX4yxxqfWKMQPZR5yoh8urCTFX88= github.com/olekukonko/tablewriter v0.0.1/go.mod h1:vsDQFd/mU46D+Z4whnwzcISnGGzXWMclvtLoiIKAKIo= -github.com/olekukonko/tablewriter v1.1.0 h1:N0LHrshF4T39KvI96fn6GT8HEjXRXYNDrDjKFDB7RIY= -github.com/olekukonko/tablewriter v1.1.0/go.mod h1:5c+EBPeSqvXnLLgkm9isDdzR3wjfBkHR9Nhfp3NWrzo= github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U= github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= github.com/opencontainers/image-spec v1.1.1 h1:y0fUlFfIZhPF1W537XOLg0/fcx6zcHCJwooC2xJA040= @@ -208,8 +184,8 @@ github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55 h1:o4JXh1EVt9k/+g42oCprj/FisM4qX9L3sZB3upGN2ZU= -github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE= +github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c h1:ncq/mPwQF4JjgDlrVEn3C11VoGHZN7m8qihwgMEtzYw= +github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE= github.com/prashantv/gostub v1.1.0 h1:BTyx3RfQjRHnUWaGF9oQos79AlQ5k8WNktv7VGvVH4g= github.com/prashantv/gostub v1.1.0/go.mod h1:A5zLQHz7ieHGG7is6LLXLz7I8+3LZzsrV0P1IAHhP5U= github.com/prometheus/client_golang v1.21.1 h1:DOvXXTqVzvkIewV/CDPFdejpMCGeMcbGCQ8YOmu+Ibk= @@ -220,23 +196,21 @@ github.com/prometheus/common v0.62.0 h1:xasJaQlnWAeyHdUBeGjXmutelfJHWMRr+Fg4QszZ github.com/prometheus/common v0.62.0/go.mod h1:vyBcEuLSvWos9B1+CyL7JZ2up+uFzXhkqml0W5zIY1I= github.com/prometheus/procfs v0.15.1 h1:YagwOFzUgYfKKHX6Dr+sHT7km/hxC76UB0learggepc= github.com/prometheus/procfs v0.15.1/go.mod h1:fB45yRUv8NstnjriLhBQLuOUt+WW4BsoGhij/e3PBqk= -github.com/redis/go-redis/v9 v9.15.0 h1:2jdes0xJxer4h3NUZrZ4OGSntGlXp4WbXju2nOTRXto= -github.com/redis/go-redis/v9 v9.15.0/go.mod h1:huWgSWd8mW6+m0VPhJjSSQ+d6Nh1VICQ6Q5lHuCH/Iw= -github.com/redis/go-redis/v9 v9.16.0 h1:OotgqgLSRCmzfqChbQyG1PHC3tLNR89DG4jdOERSEP4= -github.com/redis/go-redis/v9 v9.16.0/go.mod h1:u410H11HMLoB+TP67dz8rL9s6QW2j76l0//kSOd3370= -github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ= -github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= -github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ= -github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc= +github.com/redis/go-redis/v9 v9.14.0 h1:u4tNCjXOyzfgeLN+vAZaW1xUooqWDqVEsZN0U01jfAE= +github.com/redis/go-redis/v9 v9.14.0/go.mod h1:huWgSWd8mW6+m0VPhJjSSQ+d6Nh1VICQ6Q5lHuCH/Iw= +github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY= +github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= +github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII= +github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o= +github.com/russross/blackfriday/v2 v2.0.1 h1:lPqVAte+HuHNfhJ/0LC98ESWRz8afy9tM/0RK8m9o+Q= github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= -github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk= -github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/segmentio/ksuid v1.0.4 h1:sBo2BdShXjmcugAMwjugoGUdUV0pcxY5mW4xKRn3v4c= github.com/segmentio/ksuid v1.0.4/go.mod h1:/XUiZBD3kVx5SmUOl55voK5yeAbBNNIed+2O73XgrPE= -github.com/shirou/gopsutil/v4 v4.25.10 h1:at8lk/5T1OgtuCp+AwrDofFRjnvosn0nkN2OLQ6g8tA= -github.com/shirou/gopsutil/v4 v4.25.10/go.mod h1:+kSwyC8DRUD9XXEHCAFjK+0nuArFJM0lva+StQAcskM= +github.com/shirou/gopsutil/v4 v4.25.6 h1:kLysI2JsKorfaFPcYmcJqbzROzsBWEOAtw6A7dIfqXs= +github.com/shirou/gopsutil/v4 v4.25.6/go.mod h1:PfybzyydfZcN+JMMjkF6Zb8Mq1A/VcogFFg7hj50W9c= github.com/shopspring/decimal v1.4.0 h1:bxl37RwXBklmTi0C79JfXCEBD1cqqHt0bbgBAGFp81k= github.com/shopspring/decimal v1.4.0/go.mod h1:gawqmDU56v4yIKSwfBSFip1HdCCXN8/+DMd9qYNcwME= +github.com/shurcooL/sanitized_anchor_name v1.0.0 h1:PdmoCO6wvbs+7yrJyMORt4/BmY5IYyJwS/kOiWx8mHo= github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= @@ -259,16 +233,14 @@ github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= github.com/testcontainers/testcontainers-go v0.40.0 h1:pSdJYLOVgLE8YdUY2FHQ1Fxu+aMnb6JfVz1mxk7OeMU= github.com/testcontainers/testcontainers-go v0.40.0/go.mod h1:FSXV5KQtX2HAMlm7U3APNyLkkap35zNLxukw9oBi/MY= -github.com/tklauser/go-sysconf v0.3.15 h1:VE89k0criAymJ/Os65CSn1IXaol+1wrsFHEB8Ol49K4= -github.com/tklauser/go-sysconf v0.3.15/go.mod h1:Dmjwr6tYFIseJw7a3dRLJfsHAMXZ3nEnL/aZY+0IuI4= -github.com/tklauser/numcpus v0.10.0 h1:18njr6LDBk1zuna922MgdjQuJFjrdppsZG60sHGfjso= -github.com/tklauser/numcpus v0.10.0/go.mod h1:BiTKazU708GQTYF4mB+cmlpT2Is1gLk7XVuEeem8LsQ= +github.com/tklauser/go-sysconf v0.3.12 h1:0QaGUFOdQaIVdPgfITYzaTegZvdCjmYO52cSFAEVmqU= +github.com/tklauser/go-sysconf v0.3.12/go.mod h1:Ho14jnntGE1fpdOqQEEaiKRpvIavV0hSfmBq8nJbHYI= +github.com/tklauser/numcpus v0.6.1 h1:ng9scYS7az0Bk4OZLvrNXNSAO2Pxr1XXRAPyjhIx+Fk= +github.com/tklauser/numcpus v0.6.1/go.mod h1:1XfjsgE2zo8GVw7POkMbHENHzVg3GzmoZ9fESEdAacY= +github.com/vanng822/css v0.0.0-20190504095207-a21e860bcd04 h1:L0rPdfzq43+NV8rfIx2kA4iSSLRj2jN5ijYHoeXRwvQ= github.com/vanng822/css v0.0.0-20190504095207-a21e860bcd04/go.mod h1:tcnB1voG49QhCrwq1W0w5hhGasvOg+VQp9i9H1rCM1w= -github.com/vanng822/css v1.0.1 h1:10yiXc4e8NI8ldU6mSrWmSWMuyWgPr9DZ63RSlsgDw8= -github.com/vanng822/css v1.0.1/go.mod h1:tcnB1voG49QhCrwq1W0w5hhGasvOg+VQp9i9H1rCM1w= +github.com/vanng822/go-premailer v0.0.0-20191214114701-be27abe028fe h1:9YnI5plmy+ad6BM+JCLJb2ZV7/TNiE5l7SNKfumYKgc= github.com/vanng822/go-premailer v0.0.0-20191214114701-be27abe028fe/go.mod h1:JTFJA/t820uFDoyPpErFQ3rb3amdZoPtxcKervG0OE4= -github.com/vanng822/go-premailer v1.25.0 h1:hGHKfroCXrCDTyGVR8o4HCON5/HWvc7C1uocS+VnaZs= -github.com/vanng822/go-premailer v1.25.0/go.mod h1:8WJKIPZtegxqSOA8+eDFx7QNesKmMYfGEIodLTJqrtM= github.com/xdg-go/pbkdf2 v1.0.0 h1:Su7DPu48wXMwC3bs7MCNG+z4FhcyEuz5dlvchbq0B0c= github.com/xdg-go/pbkdf2 v1.0.0/go.mod h1:jrpuAogTd400dnrH08LKmI/xc1MbPOebTwRqcT5RDeI= github.com/xdg-go/scram v1.1.2 h1:FHX5I5B4i4hKRVRBCFRxq1iQRej7WO3hhBuJf+UUySY= @@ -286,12 +258,12 @@ github.com/zeromicro/go-zero v1.9.2 h1:ZXOXBIcazZ1pWAMiHyVnDQ3Sxwy7DYPzjE89Qtj9v github.com/zeromicro/go-zero v1.9.2/go.mod h1:k8YBMEFZKjTd4q/qO5RCW+zDgUlNyAs5vue3P4/Kmn0= go.mongodb.org/mongo-driver/v2 v2.4.0 h1:Oq6BmUAAFTzMeh6AonuDlgZMuAuEiUxoAD1koK5MuFo= go.mongodb.org/mongo-driver/v2 v2.4.0/go.mod h1:jHeEDJHJq7tm6ZF45Issun9dbogjfnPySb1vXA7EeAI= -go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64= -go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y= -go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.63.0 h1:RbKq8BG0FI8OiXhBfcRtqqHcZcka+gU3cskNuf05R18= -go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.63.0/go.mod h1:h06DGIukJOevXaj/xrNjhi/2098RZzcLTbc0jDAUbsg= -go.opentelemetry.io/otel v1.38.0 h1:RkfdswUDRimDg0m2Az18RKOsnI8UDzppJAtj01/Ymk8= -go.opentelemetry.io/otel v1.38.0/go.mod h1:zcmtmQ1+YmQM9wrNsTGV/q/uyusom3P8RxwExxkZhjM= +go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA= +go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.49.0 h1:jq9TW8u3so/bN+JPT166wjOI6/vQPF6Xe7nMNIltagk= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.49.0/go.mod h1:p8pYQP+m5XfbZm9fxtSKAbM6oIllS7s2AfxrChvc7iw= +go.opentelemetry.io/otel v1.37.0 h1:9zhNfelUvx0KBfu/gb+ZgeAfAgtWrfHJZcAqFC228wQ= +go.opentelemetry.io/otel v1.37.0/go.mod h1:ehE/umFRLnuLa/vSccNq9oS1ErUlkkK71gMcN34UG8I= go.opentelemetry.io/otel/exporters/jaeger v1.17.0 h1:D7UpUy2Xc2wsi1Ras6V40q806WM07rqoCWzXu7Sqy+4= go.opentelemetry.io/otel/exporters/jaeger v1.17.0/go.mod h1:nPCqOnEH9rNLKqH/+rrUjiMzHJdV1BlpKcTwRTyKkKI= go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.24.0 h1:t6wl9SPayj+c7lEIFgm4ooDBZVb01IhLB4InpomhRw8= @@ -304,14 +276,14 @@ go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.24.0 h1:s0PHtIkN+3xrbDO go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.24.0/go.mod h1:hZlFbDbRt++MMPCCfSJfmhkGIWnX1h3XjkfxZUjLrIA= go.opentelemetry.io/otel/exporters/zipkin v1.24.0 h1:3evrL5poBuh1KF51D9gO/S+N/1msnm4DaBqs/rpXUqY= go.opentelemetry.io/otel/exporters/zipkin v1.24.0/go.mod h1:0EHgD8R0+8yRhUYJOGR8Hfg2dpiJQxDOszd5smVO9wM= -go.opentelemetry.io/otel/metric v1.38.0 h1:Kl6lzIYGAh5M159u9NgiRkmoMKjvbsKtYRwgfrA6WpA= -go.opentelemetry.io/otel/metric v1.38.0/go.mod h1:kB5n/QoRM8YwmUahxvI3bO34eVtQf2i4utNVLr9gEmI= -go.opentelemetry.io/otel/sdk v1.38.0 h1:l48sr5YbNf2hpCUj/FoGhW9yDkl+Ma+LrVl8qaM5b+E= -go.opentelemetry.io/otel/sdk v1.38.0/go.mod h1:ghmNdGlVemJI3+ZB5iDEuk4bWA3GkTpW+DOoZMYBVVg= -go.opentelemetry.io/otel/sdk/metric v1.38.0 h1:aSH66iL0aZqo//xXzQLYozmWrXxyFkBJ6qT5wthqPoM= -go.opentelemetry.io/otel/sdk/metric v1.38.0/go.mod h1:dg9PBnW9XdQ1Hd6ZnRz689CbtrUp0wMMs9iPcgT9EZA= -go.opentelemetry.io/otel/trace v1.38.0 h1:Fxk5bKrDZJUH+AMyyIXGcFAPah0oRcT+LuNtJrmcNLE= -go.opentelemetry.io/otel/trace v1.38.0/go.mod h1:j1P9ivuFsTceSWe1oY+EeW3sc+Pp42sO++GHkg4wwhs= +go.opentelemetry.io/otel/metric v1.37.0 h1:mvwbQS5m0tbmqML4NqK+e3aDiO02vsf/WgbsdpcPoZE= +go.opentelemetry.io/otel/metric v1.37.0/go.mod h1:04wGrZurHYKOc+RKeye86GwKiTb9FKm1WHtO+4EVr2E= +go.opentelemetry.io/otel/sdk v1.37.0 h1:ItB0QUqnjesGRvNcmAcU0LyvkVyGJ2xftD29bWdDvKI= +go.opentelemetry.io/otel/sdk v1.37.0/go.mod h1:VredYzxUvuo2q3WRcDnKDjbdvmO0sCzOvVAiY+yUkAg= +go.opentelemetry.io/otel/sdk/metric v1.37.0 h1:90lI228XrB9jCMuSdA0673aubgRobVZFhbjxHHspCPc= +go.opentelemetry.io/otel/sdk/metric v1.37.0/go.mod h1:cNen4ZWfiD37l5NhS+Keb5RXVWZWpRE+9WyVCpbo5ps= +go.opentelemetry.io/otel/trace v1.37.0 h1:HLdcFNbRQBE2imdSEgm/kwqmQj1Or1l/7bW6mxVK7z4= +go.opentelemetry.io/otel/trace v1.37.0/go.mod h1:TlgrlQ+PtQO5XFerSPUYG0JSgGyryXewPGyayAWSBS0= go.opentelemetry.io/proto/otlp v1.3.1 h1:TrMUixzpM0yuc/znrFTP9MMRh8trP93mkCiDVeXrui0= go.opentelemetry.io/proto/otlp v1.3.1/go.mod h1:0X1WI4de4ZsLrrJNLAQbFeLCm3T7yBkR0XqQ7niQU+8= go.uber.org/automaxprocs v1.6.0 h1:O3y2/QNTOdbF+e/dpXNNW7Rx2hZ4sTIPyybbxyNqTUs= @@ -323,37 +295,18 @@ go.uber.org/mock v0.6.0/go.mod h1:KiVJ4BqZJaMj4svdfmHM0AUx4NJYO8ZNpPnZn1Z+BBU= golang.org/x/crypto v0.0.0-20181029175232-7e6ffbd03851/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= -golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc= -golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU= -golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8= -golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk= golang.org/x/crypto v0.43.0 h1:dduJYIi3A3KOfdGOHX8AVZ/jGiyPa3IbBozJ5kNuE04= golang.org/x/crypto v0.43.0/go.mod h1:BFbav4mRNlXJL4wNeejLpWxB7wMbc79PdRGhWKncxR0= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= -golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= -golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= -golang.org/x/mod v0.15.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= -golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= golang.org/x/net v0.0.0-20180218175443-cbe0f9307d01/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= -golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= -golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= -golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk= -golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44= -golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM= -golang.org/x/net v0.33.0/go.mod h1:HXLR5J+9DxmrqMwG9qjGCxZ+zKXxBru04zlTvWlWuN4= -golang.org/x/net v0.46.0 h1:giFlY12I07fugqwPuWJi68oOnpfqFnJIJzaIIm2JVV4= -golang.org/x/net v0.46.0/go.mod h1:Q9BGdFy1y4nkUwiLvT5qtyhAnEHgnQ/zd8PfU6nc210= +golang.org/x/net v0.45.0 h1:RLBg5JKixCy82FtLJpeNlVM0nrSqpCRYzVU1n8kj0tM= +golang.org/x/net v0.45.0/go.mod h1:ECOoLqd5U3Lhyeyo/QDCEVQ4sNgYsqvCZ722XogGieY= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y= -golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= -golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= -golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sync v0.17.0 h1:l60nONMj9l5drqw6jlhIELNv9I0A4OFgRsG9k2oT9Ug= golang.org/x/sync v0.17.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -366,25 +319,14 @@ golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.37.0 h1:fdNQudmxPjkdUTPnLn5mdQv7Zwvbvpaxqs831goi9kQ= golang.org/x/sys v0.37.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= -golang.org/x/telemetry v0.0.0-20240228155512-f48c80bd79b2/go.mod h1:TeRTkGYfJXctD9OcfyVLyj2J3IxLnKwHJR8f4D8a3YE= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= -golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= -golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo= -golang.org/x/term v0.12.0/go.mod h1:owVbMEjm3cBLCHdkQu9b1opXd4ETQWc3BhuQGKgXgvU= -golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk= -golang.org/x/term v0.20.0/go.mod h1:8UkIAJTvZgivsXaD6/pH6U9ecQzZ45awqEOzuCvwpFY= -golang.org/x/term v0.27.0/go.mod h1:iMsnZpn0cago0GOrHO2+Y7u7JPn5AylBrcoWkElMTSM= golang.org/x/term v0.36.0 h1:zMPR+aF8gfksFprF/Nc/rd1wRS1EI6nDBGyWAvDzx2Q= golang.org/x/term v0.36.0/go.mod h1:Qu394IJq6V6dCBRgwqshf3mPF85AqzYEzofzRdZkWss= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= @@ -392,12 +334,6 @@ golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ= -golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= -golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= -golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= -golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= -golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= -golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ= golang.org/x/text v0.30.0 h1:yznKA/E9zq54KzlzBEAWn1NXSQ8DIp/NYMy88xJjl4k= golang.org/x/text v0.30.0/go.mod h1:yDdHFIX9t+tORqspjENWgzaCVXgk0yYnYuSZ8UzzBVM= golang.org/x/time v0.10.0 h1:3usCWA8tQn0L8+hFJQNgzpWbd89begxN66o1Ojdn5L4= @@ -405,16 +341,14 @@ golang.org/x/time v0.10.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= -golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= -golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58= -golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk= gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E= google.golang.org/genproto/googleapis/api v0.0.0-20250804133106-a7a43d27e69b h1:ULiyYQ0FdsJhwwZUwbaXpZF5yUE3h+RA+gxvBu37ucc= google.golang.org/genproto/googleapis/api v0.0.0-20250804133106-a7a43d27e69b/go.mod h1:oDOGiMSXHL4sDTJvFvIB9nRQCGdLP1o/iVaqQK8zB+M= -google.golang.org/genproto/googleapis/rpc v0.0.0-20251103181224-f26f9409b101 h1:tRPGkdGHuewF4UisLzzHHr1spKw92qLM98nIzxbC0wY= -google.golang.org/genproto/googleapis/rpc v0.0.0-20251103181224-f26f9409b101/go.mod h1:7i2o+ce6H/6BluujYR+kqX3GKH+dChPTQU19wjRPiGk= +google.golang.org/genproto/googleapis/rpc v0.0.0-20250804133106-a7a43d27e69b h1:zPKJod4w6F1+nRGDI9ubnXYhU9NSWoFAijkHkUXeTK8= +google.golang.org/genproto/googleapis/rpc v0.0.0-20250804133106-a7a43d27e69b/go.mod h1:qQ0YXyHHx3XkvlzUtpXDkS29lDSafHMZBAZDc03LQ3A= google.golang.org/grpc v1.76.0 h1:UnVkv1+uMLYXoIz6o7chp59WfQUYA2ex/BXQ9rHZu7A= google.golang.org/grpc v1.76.0/go.mod h1:Ju12QI8M6iQJtbcsV+awF5a4hfJMLi4X0JLo94ULZ6c= google.golang.org/protobuf v1.36.10 h1:AYd7cD/uASjIL6Q9LiTjz8JLcrh/88q5UObnmY3aOOE= diff --git a/internal/config/config.go b/internal/config/config.go index e52fb9f..55708db 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -78,4 +78,23 @@ type Config struct { // 預設角色名稱 DefaultRoleName string } + SMTPConfig struct { + Enable bool + GoroutinePoolNum int + Host string + Port int + Username string + Password string + Sender string + SenderName string + } + + DeliveryConfig struct { + MaxRetries int `json:"max_retries"` // 最大重試次數 + InitialDelay time.Duration `json:"initial_delay"` // 初始重試延遲 (100ms) + BackoffFactor float64 `json:"backoff_factor"` // 指數退避因子 (2.0) + MaxDelay time.Duration `json:"max_delay"` // 最大延遲時間 + Timeout time.Duration `json:"timeout"` // 單次發送超時時間 + EnableHistory bool `json:"enable_history"` // 是否啟用歷史記錄 + } } diff --git a/internal/domain/const.go b/internal/domain/const.go new file mode 100644 index 0000000..7397134 --- /dev/null +++ b/internal/domain/const.go @@ -0,0 +1,3 @@ +package domain + +const DefaultBrand = "digimon" diff --git a/internal/handler/user/get_user_info_handler.go b/internal/handler/user/get_user_info_handler.go index 3d904e9..930ff60 100644 --- a/internal/handler/user/get_user_info_handler.go +++ b/internal/handler/user/get_user_info_handler.go @@ -2,7 +2,6 @@ package user import ( errs "backend/pkg/library/errors" - "backend/pkg/library/errors/code" "net/http" "backend/internal/logic/user" @@ -45,11 +44,7 @@ func GetUserInfoHandler(svcCtx *svc.ServiceContext) http.HandlerFunc { Error: e.Unwrap(), }) } else { - httpx.WriteJsonCtx(r.Context(), w, http.StatusOK, types.Resp{ - Code: code.SUCCESSCode, - Message: code.SUCCESSMessage, - Data: resp, - }) + httpx.WriteJsonCtx(r.Context(), w, http.StatusOK, resp) } } } diff --git a/internal/handler/user/request_verification_code_handler.go b/internal/handler/user/request_verification_code_handler.go index 01a2227..d261bc2 100644 --- a/internal/handler/user/request_verification_code_handler.go +++ b/internal/handler/user/request_verification_code_handler.go @@ -2,7 +2,6 @@ package user import ( errs "backend/pkg/library/errors" - "backend/pkg/library/errors/code" "net/http" "backend/internal/logic/user" @@ -22,7 +21,7 @@ func RequestVerificationCodeHandler(svcCtx *svc.ServiceContext) http.HandlerFunc Code: e.DisplayCode(), Message: err.Error(), }) - + return } @@ -36,11 +35,7 @@ func RequestVerificationCodeHandler(svcCtx *svc.ServiceContext) http.HandlerFunc Error: e.Unwrap(), }) } else { - httpx.WriteJsonCtx(r.Context(), w, http.StatusOK, types.Resp{ - Code: code.SUCCESSCode, - Message: code.SUCCESSMessage, - Data: resp, - }) + httpx.WriteJsonCtx(r.Context(), w, http.StatusOK, resp) } } } diff --git a/internal/handler/user/submit_verification_code_handler.go b/internal/handler/user/submit_verification_code_handler.go index 82af8a4..518f20b 100644 --- a/internal/handler/user/submit_verification_code_handler.go +++ b/internal/handler/user/submit_verification_code_handler.go @@ -2,7 +2,6 @@ package user import ( errs "backend/pkg/library/errors" - "backend/pkg/library/errors/code" "net/http" "backend/internal/logic/user" @@ -22,7 +21,7 @@ func SubmitVerificationCodeHandler(svcCtx *svc.ServiceContext) http.HandlerFunc Code: e.DisplayCode(), Message: err.Error(), }) - + return } @@ -36,11 +35,7 @@ func SubmitVerificationCodeHandler(svcCtx *svc.ServiceContext) http.HandlerFunc Error: e.Unwrap(), }) } else { - httpx.WriteJsonCtx(r.Context(), w, http.StatusOK, types.Resp{ - Code: code.SUCCESSCode, - Message: code.SUCCESSMessage, - Data: resp, - }) + httpx.WriteJsonCtx(r.Context(), w, http.StatusOK, resp) } } } diff --git a/internal/handler/user/update_password_handler.go b/internal/handler/user/update_password_handler.go index 3c5562e..5fccdd8 100644 --- a/internal/handler/user/update_password_handler.go +++ b/internal/handler/user/update_password_handler.go @@ -2,7 +2,6 @@ package user import ( errs "backend/pkg/library/errors" - "backend/pkg/library/errors/code" "net/http" "backend/internal/logic/user" @@ -22,7 +21,7 @@ func UpdatePasswordHandler(svcCtx *svc.ServiceContext) http.HandlerFunc { Code: e.DisplayCode(), Message: err.Error(), }) - + return } @@ -36,11 +35,7 @@ func UpdatePasswordHandler(svcCtx *svc.ServiceContext) http.HandlerFunc { Error: e.Unwrap(), }) } else { - httpx.WriteJsonCtx(r.Context(), w, http.StatusOK, types.Resp{ - Code: code.SUCCESSCode, - Message: code.SUCCESSMessage, - Data: resp, - }) + httpx.WriteJsonCtx(r.Context(), w, http.StatusOK, resp) } } } diff --git a/internal/handler/user/update_user_info_handler.go b/internal/handler/user/update_user_info_handler.go index 155d309..81f279b 100644 --- a/internal/handler/user/update_user_info_handler.go +++ b/internal/handler/user/update_user_info_handler.go @@ -2,7 +2,6 @@ package user import ( errs "backend/pkg/library/errors" - "backend/pkg/library/errors/code" "net/http" "backend/internal/logic/user" @@ -36,11 +35,7 @@ func UpdateUserInfoHandler(svcCtx *svc.ServiceContext) http.HandlerFunc { Error: e.Unwrap(), }) } else { - httpx.WriteJsonCtx(r.Context(), w, http.StatusOK, types.Resp{ - Code: code.SUCCESSCode, - Message: code.SUCCESSMessage, - Data: resp, - }) + httpx.WriteJsonCtx(r.Context(), w, http.StatusOK, resp) } } } diff --git a/internal/logic/auth/register_logic.go b/internal/logic/auth/register_logic.go index 4a6ac84..38fa335 100644 --- a/internal/logic/auth/register_logic.go +++ b/internal/logic/auth/register_logic.go @@ -1,6 +1,7 @@ package auth import ( + "backend/internal/domain" "backend/internal/svc" "backend/internal/types" errs "backend/pkg/library/errors" @@ -80,14 +81,12 @@ func (l *RegisterLogic) Register(req *types.LoginReq) (resp *types.LoginResp, er _, err = l.svcCtx.UserRoleUC.Assign(l.ctx, usecase.AssignRoleRequest{ RoleUID: l.svcCtx.Config.RoleConfig.DefaultRoleName, UserUID: account.UID, - Brand: "digimon", + Brand: domain.DefaultBrand, }) if err != nil { return nil, err } - // TODO 綁定 User Role - // Step 5: 生成 Token req.LoginID = bd.CreateAccountReq.LoginID tk, err := generateToken(l.svcCtx, l.ctx, req, account.UID, l.svcCtx.Config.RoleConfig.DefaultRoleName) diff --git a/internal/logic/auth/request_password_reset_logic.go b/internal/logic/auth/request_password_reset_logic.go index 3b5556f..64338d1 100644 --- a/internal/logic/auth/request_password_reset_logic.go +++ b/internal/logic/auth/request_password_reset_logic.go @@ -3,11 +3,15 @@ package auth import ( "backend/internal/domain" "backend/internal/utils" + "backend/internal/utils/email_template" errs "backend/pkg/library/errors" "backend/pkg/member/domain/member" "backend/pkg/member/domain/usecase" + notificationUC "backend/pkg/notification/domain/usecase" + "bytes" "context" "fmt" + "html/template" "backend/internal/svc" "backend/internal/types" @@ -67,13 +71,66 @@ func (l *RequestPasswordResetLogic) RequestPasswordReset(req *types.RequestPassw return nil, err } - // 發送驗證碼 - fmt.Println("======= send", vcode.Data.VerifyCode, &info) + nickname := generateMsgName(&info) + switch member.GetAccountTypeByCode(req.AccountType) { + case member.AccountTypeMail: + body, title, err := email_template.GetEmailTemplate(email_template.Language(info.PreferredLanguage), email_template.ForgetPasswordVerify) + if err != nil { + e := errs.ResNotFoundError("failed to get correct email template") + return nil, e + } - //nickname := getEmailShowName(&info) - //if err := l.sendVerificationCode(req.AccountType, acc, &info, vcode.Data.VerifyCode, nickname); err != nil { - // return nil, err - //} + tmpl, err := template.New("ForgetPasswordEmail").Parse(body) + if err != nil { + e := errs.ResInvalidFormatError("failed to get correct email template") + return nil, e + } + + emailParams := email_template.ForgetPasswordEmailReq{ + Username: nickname, + VerifyCode: vcode.Data.VerifyCode, + } + + var buf bytes.Buffer + if err := tmpl.Execute(&buf, emailParams); err != nil { + e := errs.ResInvalidFormatError("failed to build data") + return nil, e + } + + err = l.svcCtx.DeliveryUC.SendEmail(l.ctx, notificationUC.MailReq{ + To: []string{req.Identifier}, + From: l.svcCtx.Config.SMTPConfig.Sender, + SenderName: l.svcCtx.Config.SMTPConfig.SenderName, + Subject: title, + Body: buf.String(), + }) + if err != nil { + e := errs.SvcThirdPartyError("failed to send email").Wrap(err) + + return nil, e + } + case member.AccountTypePhone: + //// 送出手機號碼 + //templateResp, err := l.svcCtx.NotificationUseCase.GetSMSTemplateByTypeID( + // l.ctx, notificationModule.Language(info.PreferredLanguage), notificationModule.BindingPhone) + //if err != nil { + // return nil, err + //} + // + //fmt.Println(fmt.Sprintf("%s:%s", templateResp.Body, vcode.Data.VerifyCode)) + ////err = l.svcCtx.NotificationUseCase.SendMessage(l.ctx, notificationModule.SMSMessageRequest{ + //// PhoneNumber: acc, + //// RecipientName: nickname, + //// MessageContent: fmt.Sprintf("%s:%s", templateResp.Body, vcode), + ////}) + ////if err != nil { + //// return nil, err + ////} + case member.AccountTypeNone: + case member.AccountTypeDefine: + default: + return &types.RespOK{}, errs.InputInvalidRangeError("") + } // 設置 Redis 鍵 l.setRedisKeyWithExpiry(rk, vcode.Data.VerifyCode, 60) @@ -137,3 +194,15 @@ func (l *RequestPasswordResetLogic) setRedisKeyWithExpiry(rk, verifyCode string, }, "failed to set redis expire").Wrap(err) } } + +// generateMsgName 取得寄信用的名稱 +func generateMsgName(info *usecase.UserInfo) string { + if info.FullName != nil { + return *info.FullName + } + if info.Nickname != nil { + return *info.Nickname + } + + return info.UID +} diff --git a/internal/logic/ping/ping_logic.go b/internal/logic/ping/ping_logic.go index 2137deb..87acd16 100644 --- a/internal/logic/ping/ping_logic.go +++ b/internal/logic/ping/ping_logic.go @@ -1,9 +1,8 @@ package ping import ( - "context" - "backend/internal/svc" + "context" "github.com/zeromicro/go-zero/core/logx" ) @@ -24,7 +23,5 @@ func NewPingLogic(ctx context.Context, svcCtx *svc.ServiceContext) *PingLogic { } func (l *PingLogic) Ping() error { - // todo: add your logic here and delete this line - return nil } diff --git a/internal/logic/user/request_verification_code_logic.go b/internal/logic/user/request_verification_code_logic.go index c7e014a..ee19bae 100644 --- a/internal/logic/user/request_verification_code_logic.go +++ b/internal/logic/user/request_verification_code_logic.go @@ -1,7 +1,19 @@ package user import ( + "backend/internal/domain" + "backend/internal/utils/email_template" + errs "backend/pkg/library/errors" + mbr "backend/pkg/member/domain/member" + member "backend/pkg/member/domain/usecase" + "backend/pkg/notification/domain/usecase" + "backend/pkg/permission/domain/token" + "bytes" "context" + "fmt" + "html/template" + "regexp" + "strings" "backend/internal/svc" "backend/internal/types" @@ -25,7 +37,175 @@ func NewRequestVerificationCodeLogic(ctx context.Context, svcCtx *svc.ServiceCon } func (l *RequestVerificationCodeLogic) RequestVerificationCode(req *types.RequestVerificationCodeReq) (resp *types.RespOK, err error) { - // todo: add your logic here and delete this line + acc := "" + ct := mbr.GenerateCodeTypeEmail + switch req.Purpose { + case "email_verification": + if !isValidEmail(req.Account) { + return nil, errs.InputInvalidFormatError("email is invalid") + } + acc = req.Account + // 1. TODO 討論 email 不可以再被使用 + // 2. TODO 討論 email 跟我帳號是不是一樣(如果是用自己的信箱註冊的話) + case "phone_verification": + phone, isPhone := normalizeTaiwanMobile(req.Account) + if !isPhone { + return nil, errs.InputInvalidFormatError("phone number is invalid") + } + acc = phone + // TODO 討論號碼有被用過就不可以再被使用了 + ct = mbr.GenerateCodeTypePhone + default: + return &types.RespOK{}, errs.InputInvalidRangeError("") + } - return + uid := token.UID(l.ctx) + // 限制三分鐘內只可以發送一次 + rk := domain.GenerateVerifyCodeRedisKey.With( + fmt.Sprintf("%s-%s", uid, req.Purpose), + ).ToString() + + // 拿不到不會出錯,DB 壞掉才會 + get, err := l.svcCtx.Redis.GetCtx(l.ctx, rk) + if err != nil { + return nil, errs.DBErrorError("failed to connect to redis").Wrap(err) + } + if get != "" { + // 已經發送過驗證碼,返回提示 + return nil, errs.SysTooManyRequestError("code already sent, please wait 3min for system to send again") + } + + // 生成驗證碼 + vcode, err := l.svcCtx.AccountUC.GenerateRefreshCode(l.ctx, member.GenerateRefreshCodeRequest{ + LoginID: acc, + CodeType: ct, + }) + if err != nil { + return nil, err + } + + // 取得用戶資訊 + info, err := l.svcCtx.AccountUC.GetUserInfo(l.ctx, member.GetUserInfoRequest{ + UID: uid, + }) + if err != nil { + return nil, err + } + + nickname := generateMsgName(&info) + switch ct { + case mbr.GenerateCodeTypeEmail: + body, title, err := email_template.GetEmailTemplate(email_template.Language(info.PreferredLanguage), email_template.BindingEmail) + if err != nil { + e := errs.ResNotFoundError("failed to get correct email template") + return nil, e + } + + tmpl, err := template.New("BindEmailBody").Parse(body) + if err != nil { + e := errs.ResInvalidFormatError("failed to get correct email template") + return nil, e + } + + emailParams := email_template.ForgetPasswordEmailReq{ + Username: nickname, + VerifyCode: vcode.Data.VerifyCode, + } + + var buf bytes.Buffer + if err := tmpl.Execute(&buf, emailParams); err != nil { + e := errs.ResInvalidFormatError("failed to build data") + return nil, e + } + + err = l.svcCtx.DeliveryUC.SendEmail(l.ctx, usecase.MailReq{ + To: []string{req.Account}, + From: l.svcCtx.Config.SMTPConfig.Sender, + SenderName: l.svcCtx.Config.SMTPConfig.SenderName, + Subject: title, + Body: buf.String(), + }) + if err != nil { + e := errs.SvcThirdPartyError("failed to send email").Wrap(err) + + return nil, e + } + case mbr.GenerateCodeTypePhone: + //// 送出手機號碼 + //templateResp, err := l.svcCtx.NotificationUseCase.GetSMSTemplateByTypeID( + // l.ctx, notificationModule.Language(info.PreferredLanguage), notificationModule.BindingPhone) + //if err != nil { + // return nil, err + //} + // + //fmt.Println(fmt.Sprintf("%s:%s", templateResp.Body, vcode.Data.VerifyCode)) + ////err = l.svcCtx.NotificationUseCase.SendMessage(l.ctx, notificationModule.SMSMessageRequest{ + //// PhoneNumber: acc, + //// RecipientName: nickname, + //// MessageContent: fmt.Sprintf("%s:%s", templateResp.Body, vcode), + ////}) + ////if err != nil { + //// return nil, err + ////} + case mbr.GenerateCodeTypeNone: + case mbr.GenerateCodeTypeForgetPassword: + default: + return &types.RespOK{}, errs.InputInvalidRangeError("") + } + + // 設置 Redis 鍵,並設置 3 分鐘的過期時間 + status, err := l.svcCtx.Redis.SetnxExCtx(l.ctx, rk, vcode.Data.VerifyCode, 60*3) + if err != nil || !status { + // 純記錄,前面都已經成功,就不報錯了 + _ = errs.DBErrorErrorL(l.svcCtx.Logger, + []errs.LogField{ + {Key: "req", Val: req}, + {Key: "func", Val: "Redis.SetnxExCtx"}, + {Key: "err", Val: err.Error()}, + }, "failed to set redis expire").Wrap(err) + } + + return &types.RespOK{}, nil +} + +// 標準化號碼並驗證是否為合法台灣手機號碼 +func normalizeTaiwanMobile(phone string) (string, bool) { + // 移除空格 + phone = strings.ReplaceAll(phone, " ", "") + + // 移除 "+886" 並將剩餘部分標準化 + if strings.HasPrefix(phone, "+886") { + phone = strings.TrimPrefix(phone, "+886") + if !strings.HasPrefix(phone, "0") { + phone = "0" + phone + } + } + + // 正則表達式驗證標準化後的號碼 + regex := regexp.MustCompile(`^(09\d{8})$`) + if regex.MatchString(phone) { + return phone, true + } + + return "", false +} + +// 驗證 Email 格式的函數 +func isValidEmail(email string) bool { + // 定義正則表達式 + regex := regexp.MustCompile(`^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$`) + + return regex.MatchString(email) +} + +// generateMsgName 取得寄信用的名稱 +func generateMsgName(info *member.UserInfo) string { + if info.FullName != nil { + return *info.FullName + } + if info.Nickname != nil { + return *info.Nickname + } + + return info.UID } diff --git a/internal/logic/user/submit_verification_code_logic.go b/internal/logic/user/submit_verification_code_logic.go index e9f2e75..77cdc46 100644 --- a/internal/logic/user/submit_verification_code_logic.go +++ b/internal/logic/user/submit_verification_code_logic.go @@ -15,7 +15,7 @@ type SubmitVerificationCodeLogic struct { svcCtx *svc.ServiceContext } -// 提交驗證碼以完成驗證 +// NewSubmitVerificationCodeLogic 交驗證碼以完成驗證 func NewSubmitVerificationCodeLogic(ctx context.Context, svcCtx *svc.ServiceContext) *SubmitVerificationCodeLogic { return &SubmitVerificationCodeLogic{ Logger: logx.WithContext(ctx), diff --git a/internal/logic/user/update_user_info_logic.go b/internal/logic/user/update_user_info_logic.go index e45ca36..1a3cbb8 100644 --- a/internal/logic/user/update_user_info_logic.go +++ b/internal/logic/user/update_user_info_logic.go @@ -1,7 +1,14 @@ package user import ( + errs "backend/pkg/library/errors" + mbr "backend/pkg/member/domain/member" + member "backend/pkg/member/domain/usecase" + "backend/pkg/permission/domain/token" "context" + "fmt" + "math" + "time" "backend/internal/svc" "backend/internal/types" @@ -15,7 +22,7 @@ type UpdateUserInfoLogic struct { svcCtx *svc.ServiceContext } -// 更新當前登入的會員資訊 +// NewUpdateUserInfoLogic 更新當前登入的會員資訊 func NewUpdateUserInfoLogic(ctx context.Context, svcCtx *svc.ServiceContext) *UpdateUserInfoLogic { return &UpdateUserInfoLogic{ Logger: logx.WithContext(ctx), @@ -25,7 +32,108 @@ func NewUpdateUserInfoLogic(ctx context.Context, svcCtx *svc.ServiceContext) *Up } func (l *UpdateUserInfoLogic) UpdateUserInfo(req *types.UpdateUserInfoReq) (resp *types.UserInfoResp, err error) { - // todo: add your logic here and delete this line + update, err := ConvertBindingUserInfoToUpdateRequest(token.UID(l.ctx), req) + if err != nil { + return nil, errs.InputInvalidFormatError("failed to get correct user info", err.Error()) + } - return + err = l.svcCtx.AccountUC.UpdateUserInfo(l.ctx, update) + if err != nil { + return nil, err + } + info, err := l.svcCtx.AccountUC.GetUserInfo(l.ctx, member.GetUserInfoRequest{ + UID: token.UID(l.ctx), + }) + if err != nil { + return nil, err + } + + accountInfo, err := l.svcCtx.AccountUC.GetUserAccountInfo(l.ctx, member.GetUIDByAccountRequest{Account: token.LoginID(l.ctx)}) + if err != nil { + return nil, err + } + + res := &types.UserInfoResp{ + UID: token.UID(l.ctx), + Platform: accountInfo.Data.Platform.ToString(), + UserStatus: info.UserStatus.CodeToString(), + PreferredLanguage: info.PreferredLanguage, + Currency: info.Currency, + UpdateAt: time.Unix(0, info.CreateTime).UTC().Format(time.RFC3339), + CreateAt: time.Unix(0, info.UpdateTime).UTC().Format(time.RFC3339), + //Role string `json:"role"` + } + if info.Address != nil { + res.Address = *info.Address + } + if info.AvatarURL != nil { + res.AvatarURL = *info.AvatarURL + } + if info.FullName != nil { + res.FullName = *info.FullName + } + if info.PhoneNumber != nil { + res.PhoneNumber = *info.PhoneNumber + res.IsPhoneVerified = true + } + if info.Nickname != nil { + res.Nickname = *info.Nickname + } + if info.Email != nil { + res.Email = *info.Email + res.IsEmailVerified = true + } + if info.GenderCode != nil { + res.GenderCode = mbr.GetGenderByCode(*info.GenderCode) + } + if info.Birthdate != nil { + res.Birthdate = toStringStr(info.Birthdate) + } + + return res, nil +} + +func toStringStr(n *int64) string { + result := "" + if n != nil { + result = time.Unix(*n, 0).UTC().Format(time.RFC3339) + } + + return result +} + +func ConvertBindingUserInfoToUpdateRequest(uid string, bindingInfo *types.UpdateUserInfoReq) (*member.UpdateUserInfoRequest, error) { + updateRequest := &member.UpdateUserInfoRequest{ + UID: uid, + AvatarURL: bindingInfo.AvatarURL, + FullName: bindingInfo.FullName, + Nickname: bindingInfo.Nickname, + Address: bindingInfo.Address, + PreferredLanguage: bindingInfo.PreferredLanguage, + Currency: bindingInfo.Currency, + } + + // Convert GenderCode from string to *int8 + if &bindingInfo.GenderCode != nil { + gender := mbr.GetGenderCodeByStr(*bindingInfo.GenderCode) + // 檢查 gender 是否在 int8 範圍內 + if gender < math.MinInt8 || gender > math.MaxInt8 { + return nil, fmt.Errorf("gender code %d is out of int8 range", gender) + } + genderInt8 := int8(gender) + updateRequest.GenderCode = &genderInt8 + } + + // Convert Birthdate from string to *int64 + if &bindingInfo.Birthdate != nil { + parse, err := time.Parse(time.RFC3339, *bindingInfo.Birthdate) + if err != nil { + return nil, err + } + date := parse.Unix() + + updateRequest.Birthdate = &date + } + + return updateRequest, nil } diff --git a/internal/middleware/auth_middleware.go b/internal/middleware/auth_middleware.go index 8e8c957..ff282cd 100644 --- a/internal/middleware/auth_middleware.go +++ b/internal/middleware/auth_middleware.go @@ -2,6 +2,7 @@ package middleware import ( "backend/internal/types" + errs "backend/pkg/library/errors" "backend/pkg/permission/domain/entity" "backend/pkg/permission/domain/token" "context" @@ -34,7 +35,11 @@ func (m *AuthMiddleware) Handle(next http.HandlerFunc) http.HandlerFunc { // 解析 Header header := types.Authorization{} if err := httpx.ParseHeaders(r, &header); err != nil { - //m.writeErrorResponse(w, r, http.StatusBadRequest, "Failed to parse headers") + e := errs.AuthInvalidPosixTimeError("failed to parse headers") + httpx.WriteJsonCtx(r.Context(), w, e.HTTPStatus(), types.Resp{ + Code: e.DisplayCode(), + Message: e.Error(), + }) return } @@ -42,19 +47,23 @@ func (m *AuthMiddleware) Handle(next http.HandlerFunc) http.HandlerFunc { // 驗證 Token claim, err := uc.ParseClaims(header.Authorization, m.TokenSec, true) if err != nil { - //// 是否需要紀錄錯誤,是不是只要紀錄除了驗證失敗或過期之外的真錯誤 - //m.writeErrorResponse(w, r, - // http.StatusUnauthorized, "failed to verify toke", - // int64(100400)) + + e := errs.AuthInvalidPosixTimeError(err.Error()) + httpx.WriteJsonCtx(r.Context(), w, e.HTTPStatus(), types.Resp{ + Code: e.DisplayCode(), + Message: e.Error(), + }) return } // 驗證 Token 是否在黑名單中 if _, err := m.TokenUseCase.ValidationToken(r.Context(), entity.ValidationTokenReq{Token: header.Authorization}); err != nil { - //m.writeErrorResponse(w, r, http.StatusForbidden, - // "failed to get toke", - // int64(100400)) + e := errs.AuthUnauthorizedError("failed to use this token") + httpx.WriteJsonCtx(r.Context(), w, e.HTTPStatus(), types.Resp{ + Code: e.DisplayCode(), + Message: e.Error(), + }) return } @@ -74,11 +83,3 @@ func SetContext(r *http.Request, claim uc.TokenClaims) context.Context { return ctx } - -//// writeErrorResponse 用於處理錯誤回應 -//func (m *AuthMiddleware) writeErrorResponse(w http.ResponseWriter, r *http.Request, statusCode int, message string, code int64) { -// httpx.WriteJsonCtx(r.Context(), w, statusCode, types.Resp{ -// Code: int(code), -// Msg: message, -// }) -//} diff --git a/internal/svc/notification_model.go b/internal/svc/notification_model.go new file mode 100644 index 0000000..918acfc --- /dev/null +++ b/internal/svc/notification_model.go @@ -0,0 +1,71 @@ +package svc + +import ( + "backend/internal/config" + errs "backend/pkg/library/errors" + "backend/pkg/notification/domain/usecase" + uc "backend/pkg/notification/usecase" + + cfg "backend/pkg/notification/config" + "backend/pkg/notification/domain/repository" + rp "backend/pkg/notification/repository" +) + +func MustSMTPEmailSender(c *config.Config) repository.MailRepository { + return rp.MustSMTPUseCase(rp.SMTPMailUseCaseParam{ + Conf: cfg.SMTPConfig{ + Enable: c.SMTPConfig.Enable, + Sort: 1, + GoroutinePoolNum: c.SMTPConfig.GoroutinePoolNum, + Host: c.SMTPConfig.Host, + Port: c.SMTPConfig.Port, + Username: c.SMTPConfig.Username, + Password: c.SMTPConfig.Password, + }, + }) +} + +func MustAwsEmailSender(c *config.Config) repository.MailRepository { + return rp.MustAwsSesMailRepository(rp.AwsEmailDeliveryParam{ + Conf: &cfg.AmazonSesSettings{}, + }) +} + +func MustSMS(c *config.Config) repository.SMSClientRepository { + return rp.MustMitakeRepository(rp.MitakeSMSDeliveryParam{ + Conf: &cfg.MitakeSMSSender{}, + }) +} + +func MustDeliveryUseCase(c *config.Config, logger errs.Logger) usecase.DeliveryUseCase { + emailProviders := make([]usecase.EmailProvider, 0, 10) + smsProviders := make([]usecase.SMSProvider, 0) + //smsProviders = append(smsProviders, usecase.SMSProvider{ + // Sort: 1, + // Repo: MustSMS(c), + //}) + + //emailProviders = append(emailProviders, usecase.EmailProvider{ + // Sort: 2, + // Repo: MustAwsEmailSender(c), + //}) + + emailProviders = append(emailProviders, usecase.EmailProvider{ + Sort: 1, + Repo: MustSMTPEmailSender(c), + }) + + return uc.MustDeliveryUseCase(uc.DeliveryUseCaseParam{ + SMSProviders: smsProviders, + EmailProviders: emailProviders, + DeliveryConfig: cfg.DeliveryConfig{ + MaxRetries: c.DeliveryConfig.MaxRetries, + InitialDelay: c.DeliveryConfig.InitialDelay, + BackoffFactor: c.DeliveryConfig.BackoffFactor, + MaxDelay: c.DeliveryConfig.MaxDelay, + Timeout: c.DeliveryConfig.Timeout, + EnableHistory: false, + }, + Logger: logger, + }) +} diff --git a/internal/svc/service_context.go b/internal/svc/service_context.go index a17dc79..ff8d0dd 100644 --- a/internal/svc/service_context.go +++ b/internal/svc/service_context.go @@ -10,6 +10,7 @@ import ( vi "backend/pkg/library/validator" memberUC "backend/pkg/member/domain/usecase" + deliveryUC "backend/pkg/notification/domain/usecase" tokenUC "backend/pkg/permission/domain/usecase" "github.com/zeromicro/go-zero/core/stores/redis" @@ -26,6 +27,7 @@ type ServiceContext struct { RoleUC tokenUC.RoleUseCase RolePermission tokenUC.RolePermissionUseCase UserRoleUC tokenUC.UserRoleUseCase + DeliveryUC deliveryUC.DeliveryUseCase Redis *redis.Redis Logger errs.Logger } @@ -39,6 +41,7 @@ func NewServiceContext(c config.Config) *ServiceContext { rp := NewPermissionUC(&c) tkUC := NewTokenUC(&c, rds) + lgr := MustLogger(logx.WithContext(context.Background())) return &ServiceContext{ Config: c, @@ -54,6 +57,7 @@ func NewServiceContext(c config.Config) *ServiceContext { RolePermission: rp.RolePermission, UserRoleUC: rp.UserRole, Redis: rds, - Logger: MustLogger(logx.WithContext(context.Background())), + DeliveryUC: MustDeliveryUseCase(&c, lgr), + Logger: lgr, } } diff --git a/internal/types/types.go b/internal/types/types.go index 074da8a..8517317 100644 --- a/internal/types/types.go +++ b/internal/types/types.go @@ -82,6 +82,7 @@ type RequestPasswordResetReq struct { } type RequestVerificationCodeReq struct { + Account string `json:"account" validate:"required` Purpose string `json:"purpose" validate:"required,oneof=email_verification phone_verification"` Authorization } @@ -146,13 +147,9 @@ type UserInfoResp struct { UserStatus string `json:"user_status"` // 用戶狀態 PreferredLanguage string `json:"preferred_language"` // 偏好語言 Currency string `json:"currency"` // 偏好幣種 - National string `json:"national"` // 國家 - PostCode string `json:"post_code"` // 郵遞區號 - Carrier string `json:"carrier"` // 載具 Role string `json:"role"` // 角色 UpdateAt string `json:"update_at"` CreateAt string `json:"create_at"` - Authorization } type VerifyCodeReq struct { diff --git a/internal/utils/email_template/const.go b/internal/utils/email_template/const.go new file mode 100644 index 0000000..ba5826c --- /dev/null +++ b/internal/utils/email_template/const.go @@ -0,0 +1,32 @@ +package email_template + +import ( + "fmt" +) + +type Language string + +const ( + LanguageZhTW Language = "zh_tw" + LanguageEnUS Language = "en_us" +) + +type TypeID int64 + +func (id TypeID) String() string { + return fmt.Sprintf("%4d", id) +} + +// 驗證碼通知類 0 ~ 100 +const ( + BindingEmail TypeID = 1 // 驗證碼:綁定 Email + BindingPhone TypeID = 2 // 驗證碼:綁定 手機 + ForgetPasswordVerify TypeID = 3 // 驗證碼: 忘記密碼 +) + +var EmailTemplateMap = map[Language]map[TypeID]func() (string, string, error){ + LanguageZhTW: { + ForgetPasswordVerify: GenerateForgetPasswordEmailZHTW, + BindingEmail: GenerateBindingEmailZHTW, + }, +} diff --git a/internal/utils/email_template/get.go b/internal/utils/email_template/get.go new file mode 100644 index 0000000..f718df5 --- /dev/null +++ b/internal/utils/email_template/get.go @@ -0,0 +1,21 @@ +package email_template + +import "fmt" + +// GetEmailTemplate 取得指定的 Email 樣板 +func GetEmailTemplate(lang Language, typeID TypeID) (string, string, error) { + // 查找指定語言的模板映射 + templateByLang, exists := EmailTemplateMap[lang] + if !exists { + return "", "", fmt.Errorf("email template not found for language: %s", lang) + } + + // 查找指定類型的模板生成函數 + templateFunc, exists := templateByLang[typeID] + if !exists { + return "", "", fmt.Errorf("email template not found for type ID: %s", typeID) + } + + // 執行模板生成函數 + return templateFunc() +} diff --git a/internal/utils/email_template/template.go b/internal/utils/email_template/template.go new file mode 100644 index 0000000..0da8093 --- /dev/null +++ b/internal/utils/email_template/template.go @@ -0,0 +1,150 @@ +package email_template + +import "github.com/matcornic/hermes/v2" + +// ProductInfo 包含產品相關的資訊,用於郵件模板中的產品展示部分 +type ProductInfo struct { + Name string + Link string + Logo string + Copyright string +} + +// EmailBodyContent 包含郵件正文的資訊,用於生成郵件的主要內容 +type EmailBodyContent struct { + RecipientName string + Intros []string + Actions []hermes.Action + Outros []string + Signature string +} + +// ForgetPasswordEmailContentParams 包含生成忘記密碼郵件所需的參數 +type ForgetPasswordEmailContentParams struct { + Product ProductInfo + Content EmailBodyContent +} + +type ForgetPasswordEmailReq struct { + Username string + VerifyCode string +} + +type CooperateThanksEmailReq struct { + Username string + BusinessID string + Rewards []RewardItem + Country string + TotalAmount string +} + +type RewardItem struct { + Title string + Content string + Amount string + //Add string +} +type CooperateUserEmailReq struct { + BusinessID string `json:"business_id"` // 開案編號 + Name string `json:"name"` // 聯絡人姓名 + Phone string `json:"phone"` // 聯絡電話 + OrgName string `json:"org_name"` // 團體名稱 + Email string `json:"email"` // 電子信箱 + TargetAmount string `json:"target_amount"` // 目標募款金額 + HasPermit string `json:"has_permit"` // 是否有勸募字號 + FundRequest string `json:"fund_request"` // 募款需求 +} + +const ( + nTw = "Digimon 團隊" + link = "https://code.30cm.net" + logo = "https://true-heart-dev.s3.ap-northeast-3.amazonaws.com/f70904eb-1a29-40f7-8940-9a124f23793a.png" + cryptoTw = "© 2025~ Digimon Inc. 版權所有" +) + +// GenerateForgetPasswordEmailZHTW 生成繁體中文的忘記密碼驗證信 +func GenerateForgetPasswordEmailZHTW() (string, string, error) { + req := ForgetPasswordEmailContentParams{ + Product: ProductInfo{ + Name: nTw, + Link: link, + Logo: logo, + Copyright: cryptoTw, + }, + Content: EmailBodyContent{ + RecipientName: "{{.Username}}", + Intros: []string{ + "您收到此電子郵件是因為我們收到了針對帳戶的密碼重置請求。", + }, + Actions: []hermes.Action{ + { + Instructions: "請複製您的驗證碼,到網頁重置", + InviteCode: "{{.VerifyCode}}", + }, + }, + Outros: []string{ + "如果您不要求重設密碼,則無需您採取進一步的措施。", + }, + Signature: "", + }, + } + + emailBody, err := buildForgetPasswordEmailContent(req) + + return emailBody, "Digimon 重設密碼驗證信", err +} + +// GenerateBindingEmailZHTW 生成綁定帳號驗證信 +func GenerateBindingEmailZHTW() (string, string, error) { + req := ForgetPasswordEmailContentParams{ + Product: ProductInfo{ + Name: nTw, + Link: link, + Logo: logo, + Copyright: cryptoTw, + }, + Content: EmailBodyContent{ + RecipientName: "{{.Username}}", + Intros: []string{ + "您收到此電子郵件是因為我們收到了針對帳戶的Email認證請求。", + }, + Actions: []hermes.Action{ + { + Instructions: "請複製您的驗證碼,到網頁重置", + InviteCode: "{{.VerifyCode}}", + }, + }, + Outros: []string{ + "如果您不要求重設密碼,則無需您採取進一步的措施。", + }, + Signature: "", + }, + } + + emailBody, err := buildForgetPasswordEmailContent(req) + + return emailBody, "Digimon 綁定信箱驗證信", err +} + +// buildForgetPasswordEmailContent 根據參數生成忘記密碼郵件的產品和內容結構 +func buildForgetPasswordEmailContent(params ForgetPasswordEmailContentParams) (string, error) { + product := hermes.Product{ + Name: params.Product.Name, + Link: params.Product.Link, + Logo: params.Product.Logo, + Copyright: params.Product.Copyright, + } + + body := hermes.Body{ + Name: params.Content.RecipientName, + Intros: params.Content.Intros, + Actions: params.Content.Actions, + Outros: params.Content.Outros, + Signature: params.Content.Signature, + } + + h := hermes.Hermes{Product: product} + email := hermes.Email{Body: body} + + return h.GenerateHTML(email) +} diff --git a/pkg/notification/domain/error.go b/pkg/notification/domain/error.go new file mode 100644 index 0000000..4188b5a --- /dev/null +++ b/pkg/notification/domain/error.go @@ -0,0 +1 @@ +package domain diff --git a/pkg/notification/domain/repository/mail.go b/pkg/notification/domain/repository/mail.go index f95f764..b4d8379 100644 --- a/pkg/notification/domain/repository/mail.go +++ b/pkg/notification/domain/repository/mail.go @@ -8,8 +8,9 @@ type MailRepository interface { } type MailReq struct { - To []string - From string - Subject string - Body string + To []string + From string + Subject string + Body string + SenderName string } diff --git a/pkg/notification/domain/usecase/delivery.go b/pkg/notification/domain/usecase/delivery.go index c4d07a7..24528b2 100644 --- a/pkg/notification/domain/usecase/delivery.go +++ b/pkg/notification/domain/usecase/delivery.go @@ -11,10 +11,11 @@ type DeliveryUseCase interface { } type MailReq struct { - To []string - From string - Subject string - Body string + To []string + From string + SenderName string + Subject string + Body string } type SMSMessageRequest struct { diff --git a/pkg/notification/repository/smtp_mailer.go b/pkg/notification/repository/smtp_mailer.go index a589eea..02b2339 100644 --- a/pkg/notification/repository/smtp_mailer.go +++ b/pkg/notification/repository/smtp_mailer.go @@ -4,6 +4,7 @@ import ( "backend/pkg/notification/config" "backend/pkg/notification/domain/repository" "context" + "fmt" "gopkg.in/gomail.v2" ) @@ -32,9 +33,14 @@ func (repo *SMTPMailRepository) SendMail(ctx context.Context, req repository.Mai return ctx.Err() } + sender := req.From + if req.SenderName != "" { + sender = fmt.Sprintf("%s <%s>", req.SenderName, req.From) + } + // 構建郵件 m := gomail.NewMessage() - m.SetHeader("From", req.From) + m.SetHeader("From", sender) m.SetHeader("To", req.To...) m.SetHeader("Subject", req.Subject) m.SetBody("text/html", req.Body) diff --git a/pkg/notification/usecase/delivery.go b/pkg/notification/usecase/delivery.go index cfbf01b..9461f68 100644 --- a/pkg/notification/usecase/delivery.go +++ b/pkg/notification/usecase/delivery.go @@ -174,10 +174,11 @@ func (a *emailProviderAdapter) getProviderSort(index int) int64 { func (a *emailProviderAdapter) send(ctx context.Context, providerIndex int) error { return a.providers[providerIndex].Repo.SendMail(ctx, repository.MailReq{ - From: a.request.From, - To: a.request.To, - Subject: a.request.Subject, - Body: a.request.Body, + From: a.request.From, + To: a.request.To, + Subject: a.request.Subject, + Body: a.request.Body, + SenderName: a.request.SenderName, }) }