v0.0.25 Добавлен gRPC сервис подписок/пидписчиков
All checks were successful
continuous-integration/drone/push Build is passing

This commit is contained in:
admin 2025-08-26 09:48:50 +03:00
parent 3c0cd21139
commit db403171da
19 changed files with 6880 additions and 412 deletions

3
.env
View File

@ -11,4 +11,5 @@ SMTP_USERNAME=info@tailly.ru
SMTP_PASSWORD="86AN1z1WxbFX6Q9u"
SMTP_FROM=info@tailly.ru
AppURL="https://tailly.ru"
MESSAGE_SERVICE_ADDRESS="tailly_messages:50052"
MESSAGE_SERVICE_ADDRESS="tailly_messages:50052"
SUBSCRIBE_SERVICE_ADDRESS="tailly_subscribers:50053"

View File

@ -1,4 +1,4 @@
FROM golang:1.25rc2-alpine AS builder
FROM golang:1.25rc3-alpine AS builder
RUN apk add --no-cache git ca-certificates build-base

View File

@ -33,25 +33,47 @@ func main() {
}
defer db.Close()
grpcConn, err := grpc.Dial(
// Подключение к MessageService gRPC
messageConn, err := grpc.Dial(
cfg.GRPC.MessageServiceAddress,
grpc.WithInsecure(),
grpc.WithKeepaliveParams(keepalive.ClientParameters{
Time: 30 * time.Second, // Отправлять keepalive ping каждые 30 секунд
Timeout: 10 * time.Second, // Ждать ответ 10 секунд
PermitWithoutStream: true, // Разрешить keepalive даже без активных стримов
Time: 30 * time.Second,
Timeout: 10 * time.Second,
PermitWithoutStream: true,
}),
grpc.WithConnectParams(grpc.ConnectParams{
Backoff: backoff.DefaultConfig, // Экспоненциальная backoff-стратегия
MinConnectTimeout: 5 * time.Second, // Минимальное время ожидания подключения
Backoff: backoff.DefaultConfig,
MinConnectTimeout: 5 * time.Second,
}),
grpc.WithDefaultServiceConfig(`{"loadBalancingPolicy":"round_robin"}`), // Политика балансировки
grpc.WithDefaultServiceConfig(`{"loadBalancingPolicy":"round_robin"}`),
)
if err != nil {
log.Fatalf("failed to connect to messages gRPC service: %v", err)
}
defer grpcConn.Close()
messageClient := proto.NewMessageServiceClient(grpcConn)
defer messageConn.Close()
messageClient := proto.NewMessageServiceClient(messageConn)
// Подключение к SubscribeService gRPC
subscribeConn, err := grpc.Dial(
cfg.GRPC.SubscribeServiceAddress, // Добавьте этот параметр в конфиг
grpc.WithInsecure(),
grpc.WithKeepaliveParams(keepalive.ClientParameters{
Time: 30 * time.Second,
Timeout: 10 * time.Second,
PermitWithoutStream: true,
}),
grpc.WithConnectParams(grpc.ConnectParams{
Backoff: backoff.DefaultConfig,
MinConnectTimeout: 5 * time.Second,
}),
grpc.WithDefaultServiceConfig(`{"loadBalancingPolicy":"round_robin"}`),
)
if err != nil {
log.Fatalf("failed to connect to subscribe gRPC service: %v", err)
}
defer subscribeConn.Close()
subscribeClient := proto.NewSubscribeServiceClient(subscribeConn)
// Инициализация зависимостей
tokenAuth := auth.NewTokenAuth(
@ -96,19 +118,20 @@ func main() {
// Создаем структуру Services
services := &service.Services{
Auth: authService,
User: userService,
Post: postService,
Comment: commentService,
Like: likeService,
Audit: auditService,
Recovery: recoveryService,
Session: sessionService,
Mail: mailService,
Messages: messageClient, // Добавляем gRPC клиент
Auth: authService,
User: userService,
Post: postService,
Comment: commentService,
Like: likeService,
Audit: auditService,
Recovery: recoveryService,
Session: sessionService,
Mail: mailService,
Messages: messageClient,
Subscribe: subscribeClient, // Добавляем gRPC клиент подписок
}
// HTTP сервер - передаем db как дополнительный параметр
// HTTP сервер
server := http.NewServer(cfg, services, tokenAuth, db)
// Запуск сервера в отдельной горутине

79
go.mod
View File

@ -1,45 +1,100 @@
module tailly_back_v2
go 1.25rc2
go 1.25rc3
require (
github.com/99designs/gqlgen v0.17.77
github.com/aws/aws-sdk-go v1.55.7
github.com/aws/aws-sdk-go v1.55.8
github.com/caarlos0/env/v8 v8.0.0
github.com/go-chi/chi/v5 v5.2.2
github.com/golang-jwt/jwt/v5 v5.2.3
github.com/golang-jwt/jwt/v5 v5.3.0
github.com/gorilla/websocket v1.5.3
github.com/jackc/pgx/v4 v4.18.3
github.com/joho/godotenv v1.5.1
github.com/lib/pq v1.10.9
github.com/prometheus/client_golang v1.22.0
github.com/prometheus/client_golang v1.23.0
github.com/stretchr/testify v1.10.0
github.com/testcontainers/testcontainers-go v0.38.0
github.com/vektah/gqlparser/v2 v2.5.30
golang.org/x/crypto v0.40.0
google.golang.org/grpc v1.74.2
google.golang.org/protobuf v1.36.6
golang.org/x/crypto v0.41.0
google.golang.org/grpc v1.75.0
google.golang.org/protobuf v1.36.8
gopkg.in/gomail.v2 v2.0.0-20160411212932-81ebce5c23df
)
require (
dario.cat/mergo v1.0.1 // indirect
github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 // indirect
github.com/Microsoft/go-winio v0.6.2 // indirect
github.com/agnivade/levenshtein v1.2.1 // indirect
github.com/beorn7/perks v1.0.1 // indirect
github.com/cenkalti/backoff/v4 v4.2.1 // indirect
github.com/cespare/xxhash/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
github.com/containerd/platforms v0.2.1 // indirect
github.com/cpuguy83/dockercfg v0.3.2 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/distribution/reference v0.6.0 // indirect
github.com/docker/docker v28.2.2+incompatible // indirect
github.com/docker/go-connections v0.5.0 // indirect
github.com/docker/go-units v0.5.0 // indirect
github.com/ebitengine/purego v0.8.4 // indirect
github.com/felixge/httpsnoop v1.0.4 // 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.2.6 // indirect
github.com/go-viper/mapstructure/v2 v2.4.0 // indirect
github.com/gogo/protobuf v1.3.2 // indirect
github.com/google/uuid v1.6.0 // indirect
github.com/hashicorp/golang-lru/v2 v2.0.7 // indirect
github.com/jackc/chunkreader/v2 v2.0.1 // indirect
github.com/jackc/pgconn v1.14.3 // indirect
github.com/jackc/pgio v1.0.0 // indirect
github.com/jackc/pgpassfile v1.0.0 // indirect
github.com/jackc/pgproto3/v2 v2.3.3 // indirect
github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a // indirect
github.com/jackc/pgtype v1.14.0 // indirect
github.com/jackc/puddle v1.3.0 // indirect
github.com/jmespath/go-jmespath v0.4.0 // indirect
github.com/kr/text v0.2.0 // indirect
github.com/klauspost/compress v1.18.0 // indirect
github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 // indirect
github.com/magiconair/properties v1.8.10 // 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.0 // indirect
github.com/morikuni/aec v1.0.0 // indirect
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
github.com/opencontainers/go-digest v1.0.0 // indirect
github.com/opencontainers/image-spec v1.1.1 // 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-20210106213030-5aafc221ea8c // indirect
github.com/prometheus/client_model v0.6.2 // indirect
github.com/prometheus/common v0.65.0 // indirect
github.com/prometheus/procfs v0.17.0 // indirect
github.com/shirou/gopsutil/v4 v4.25.5 // indirect
github.com/sirupsen/logrus v1.9.3 // indirect
github.com/sosodev/duration v1.3.1 // indirect
golang.org/x/net v0.42.0 // indirect
golang.org/x/sys v0.34.0 // indirect
golang.org/x/text v0.27.0 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20250721164621-a45f3dfb1074 // indirect
github.com/tklauser/go-sysconf v0.3.12 // indirect
github.com/tklauser/numcpus v0.6.1 // indirect
github.com/yusufpapurcu/wmi v1.2.4 // 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/otlp/otlptrace v1.37.0 // indirect
go.opentelemetry.io/otel/metric v1.37.0 // indirect
go.opentelemetry.io/otel/trace v1.37.0 // indirect
go.opentelemetry.io/proto/otlp v1.7.1 // indirect
golang.org/x/net v0.43.0 // indirect
golang.org/x/sys v0.35.0 // indirect
golang.org/x/text v0.28.0 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20250818200422-3122310a409c // indirect
gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)

355
go.sum
View File

@ -1,5 +1,15 @@
dario.cat/mergo v1.0.1 h1:Ra4+bf83h2ztPIQYNP99R6m+Y7KfnARDfID+a+vLl4s=
dario.cat/mergo v1.0.1/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk=
github.com/99designs/gqlgen v0.17.77 h1:zVW7iyRc9Z+qhvXx2wFFDpHub/E0I0Sz4/Ul2mQ61Pc=
github.com/99designs/gqlgen v0.17.77/go.mod h1:yI/o31IauG2kX0IsskM4R894OCCG1jXJORhtLQqB7Oc=
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-20210617225240-d185dfc1b5a1 h1:UQHMgLO+TxOElx5B5HZ4hJQsoJ/PvUvKRhJHDQXO8P8=
github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/Masterminds/semver/v3 v3.1.1/go.mod h1:VPu/7SZ7ePZ3QOrcuXROw5FAcLl4a0cBrbBpGY/8hQs=
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.10.3 h1:pFYcNSqHxBD06Fpj/KsbStFRsgRATgnf3LeXiUkhzPo=
github.com/PuerkitoBio/goquery v1.10.3/go.mod h1:tMUX0zDMHXYlAQk6p35XxQMqMweEKB7iK7iLNd4RH4Y=
github.com/agnivade/levenshtein v1.2.1 h1:EHBY3UOn1gwdy/VbFwgo4cxecRznFk7fKWN1KOX7eoM=
@ -10,114 +20,393 @@ github.com/andybalholm/cascadia v1.3.3 h1:AG2YHrzJIm4BZ19iwJ/DAua6Btl3IwJX+VI4kk
github.com/andybalholm/cascadia v1.3.3/go.mod h1:xNd9bqTn98Ln4DwST8/nG+H0yuB8Hmgu1YHNnWw0GeA=
github.com/arbovm/levenshtein v0.0.0-20160628152529-48b4e1c0c4d0 h1:jfIu9sQUG6Ig+0+Ap1h4unLjW6YQJpKZVmUzxsD4E/Q=
github.com/arbovm/levenshtein v0.0.0-20160628152529-48b4e1c0c4d0/go.mod h1:t2tdKJDJF9BV14lnkjHmOQgcvEKgtqs5a1N3LNdJhGE=
github.com/aws/aws-sdk-go v1.55.7 h1:UJrkFq7es5CShfBwlWAC8DA077vp8PyVbQd3lqLiztE=
github.com/aws/aws-sdk-go v1.55.7/go.mod h1:eRwEWoyTWFMVYVQzKMNHWP5/RV4xIUGMQfXQHfHkpNU=
github.com/aws/aws-sdk-go v1.55.8 h1:JRmEUbU52aJQZ2AjX4q4Wu7t4uZjOu71uyNmaWlUkJQ=
github.com/aws/aws-sdk-go v1.55.8/go.mod h1:ZkViS9AqA6otK+JBBNH2++sx1sgxrPKcSzPPvQkUtXk=
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
github.com/caarlos0/env/v8 v8.0.0 h1:POhxHhSpuxrLMIdvTGARuZqR4Jjm8AYmoi/JKlcScs0=
github.com/caarlos0/env/v8 v8.0.0/go.mod h1:7K4wMY9bH0esiXSSHlfHLX5xKGQMnkH5Fk4TDSSSzfo=
github.com/cenkalti/backoff/v4 v4.2.1 h1:y4OZtCnogmCPw98Zjyt5a6+QwPLGkiQsYW5oUqylYbM=
github.com/cenkalti/backoff/v4 v4.2.1/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/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
github.com/cockroachdb/apd v1.1.0 h1:3LFP3629v+1aKXU5Q37mxmRxX/pIu1nijXydLShEq5I=
github.com/cockroachdb/apd v1.1.0/go.mod h1:8Sl8LxpKi29FqWXR16WEFZRNSz3SoPzUzeMeY4+DwBQ=
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=
github.com/containerd/errdefs/pkg v0.3.0/go.mod h1:NJw6s9HwNuRhnjJhM7pylWwMyAkmCQvQ4GpJHEqRLVk=
github.com/containerd/log v0.1.0 h1:TCJt7ioM2cr/tfR8GPbGf9/VRAX8D2B4PjzCpfX540I=
github.com/containerd/log v0.1.0/go.mod h1:VRRf09a7mHDIRezVKTRCrOq78v577GXq3bSa3EhrzVo=
github.com/containerd/platforms v0.2.1 h1:zvwtM3rz2YHPQsF2CHYM8+KtB5dvhISiXh5ZpSBQv6A=
github.com/containerd/platforms v0.2.1/go.mod h1:XHCb+2/hzowdiut9rkudds9bE5yJ7npe7dG/wG+uFPw=
github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
github.com/coreos/go-systemd v0.0.0-20190719114852-fd7a80b32e1f/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
github.com/cpuguy83/dockercfg v0.3.2 h1:DlJTyZGBDlXqUZ2Dk2Q3xHs/FtnooJJVaad2S9GKorA=
github.com/cpuguy83/dockercfg v0.3.2/go.mod h1:sugsbF4//dDlL/i+S+rtpIWp+5h0BHJHfjj5/jFyUJc=
github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY=
github.com/creack/pty v1.1.18 h1:n56/Zwd5o6whRC5PMGretI4IdRLlmBXYNjScPaBgsbY=
github.com/creack/pty v1.1.18/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/dgryski/trifles v0.0.0-20230903005119-f50d829f2e54 h1:SG7nF6SRlWhcT7cNTs5R6Hk4V2lcmLz2NsG2VnInyNo=
github.com/dgryski/trifles v0.0.0-20230903005119-f50d829f2e54/go.mod h1:if7Fbed8SFyPtHLHbg49SI7NAdJiC5WIA09pe59rfAA=
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.2.2+incompatible h1:CjwRSksz8Yo4+RmQ339Dp/D2tGO5JxwYeqtMOEe0LDw=
github.com/docker/docker v28.2.2+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
github.com/docker/go-connections v0.5.0 h1:USnMq7hx7gwdVZq1L49hLXaFtUdTADjXGp+uj1Br63c=
github.com/docker/go-connections v0.5.0/go.mod h1:ov60Kzw0kKElRwhNs9UlUHAE/F9Fe6GLaXnqyDdmEXc=
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.8.4 h1:CF7LEKg5FFOsASUj0+QwaXf8Ht6TlFxg09+S9wz0omw=
github.com/ebitengine/purego v0.8.4/go.mod h1:iIjxzd6CiRiOG0UyXP+V1+jWqUXVjPKLAI0mRfJZTmQ=
github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg=
github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
github.com/go-chi/chi/v5 v5.2.2 h1:CMwsvRVTbXVytCk1Wd72Zy1LAsAh9GxMmSNWLHCG618=
github.com/go-chi/chi/v5 v5.2.2/go.mod h1:L2yAIGWB3H+phAw1NxKwWM+7eUH/lU8pOMm5hHcoops=
github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY=
github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A=
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-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
github.com/go-viper/mapstructure/v2 v2.4.0 h1:EBsztssimR/CONLSZZ04E8qAkxNYq4Qp9LvH92wZUgs=
github.com/go-viper/mapstructure/v2 v2.4.0/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM=
github.com/golang-jwt/jwt/v5 v5.2.3 h1:kkGXqQOBSDDWRhWNXTFpqGSCMyh/PLnqUvMGJPDJDs0=
github.com/golang-jwt/jwt/v5 v5.2.3/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk=
github.com/gofrs/uuid v4.0.0+incompatible h1:1SD/1F5pU8p29ybwgQSwpQk+mwdRrXCYuPhW6m+TnJw=
github.com/gofrs/uuid v4.0.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM=
github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
github.com/golang-jwt/jwt/v5 v5.3.0 h1:pv4AsKCKKZuqlgs5sUmn4x8UlGa0kEVt/puTpKx9vvo=
github.com/golang-jwt/jwt/v5 v5.3.0/go.mod h1:fxCRLWMO43lRc8nhHWY6LGqRcf+1gQWArsqaEUEa5bE=
github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
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/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
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/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg=
github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.1 h1:X5VWvz21y3gzm9Nw/kaUeku/1+uBhcekkmy4IkffJww=
github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.1/go.mod h1:Zanoh4+gvIgluNqcfMVTJueD4wSS5hT7zTt4Mrutd90=
github.com/hashicorp/golang-lru/v2 v2.0.7 h1:a+bsQ5rvGLjzHuww6tVxozPZFVghXaHOwFs4luLUK2k=
github.com/hashicorp/golang-lru/v2 v2.0.7/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM=
github.com/jackc/chunkreader v1.0.0/go.mod h1:RT6O25fNZIuasFJRyZ4R/Y2BbhasbmZXF9QQ7T3kePo=
github.com/jackc/chunkreader/v2 v2.0.0/go.mod h1:odVSm741yZoC3dpHEUXIqA9tQRhFrgOHwnPIn9lDKlk=
github.com/jackc/chunkreader/v2 v2.0.1 h1:i+RDz65UE+mmpjTfyz0MoVTnzeYxroil2G82ki7MGG8=
github.com/jackc/chunkreader/v2 v2.0.1/go.mod h1:odVSm741yZoC3dpHEUXIqA9tQRhFrgOHwnPIn9lDKlk=
github.com/jackc/pgconn v0.0.0-20190420214824-7e0022ef6ba3/go.mod h1:jkELnwuX+w9qN5YIfX0fl88Ehu4XC3keFuOJJk9pcnA=
github.com/jackc/pgconn v0.0.0-20190824142844-760dd75542eb/go.mod h1:lLjNuW/+OfW9/pnVKPazfWOgNfH2aPem8YQ7ilXGvJE=
github.com/jackc/pgconn v0.0.0-20190831204454-2fabfa3c18b7/go.mod h1:ZJKsE/KZfsUgOEh9hBm+xYTstcNHg7UPMVJqRfQxq4s=
github.com/jackc/pgconn v1.8.0/go.mod h1:1C2Pb36bGIP9QHGBYCjnyhqu7Rv3sGshaQUvmfGIB/o=
github.com/jackc/pgconn v1.9.0/go.mod h1:YctiPyvzfU11JFxoXokUOOKQXQmDMoJL9vJzHH8/2JY=
github.com/jackc/pgconn v1.9.1-0.20210724152538-d89c8390a530/go.mod h1:4z2w8XhRbP1hYxkpTuBjTS3ne3J48K83+u0zoyvg2pI=
github.com/jackc/pgconn v1.14.3 h1:bVoTr12EGANZz66nZPkMInAV/KHD2TxH9npjXXgiB3w=
github.com/jackc/pgconn v1.14.3/go.mod h1:RZbme4uasqzybK2RK5c65VsHxoyaml09lx3tXOcO/VM=
github.com/jackc/pgio v1.0.0 h1:g12B9UwVnzGhueNavwioyEEpAmqMe1E/BN9ES+8ovkE=
github.com/jackc/pgio v1.0.0/go.mod h1:oP+2QK2wFfUWgr+gxjoBH9KGBb31Eio69xUb0w5bYf8=
github.com/jackc/pgmock v0.0.0-20190831213851-13a1b77aafa2/go.mod h1:fGZlG77KXmcq05nJLRkk0+p82V8B8Dw8KN2/V9c/OAE=
github.com/jackc/pgmock v0.0.0-20201204152224-4fe30f7445fd/go.mod h1:hrBW0Enj2AZTNpt/7Y5rr2xe/9Mn757Wtb2xeBzPv2c=
github.com/jackc/pgmock v0.0.0-20210724152146-4ad1a8207f65 h1:DadwsjnMwFjfWc9y5Wi/+Zz7xoE5ALHsRQlOctkOiHc=
github.com/jackc/pgmock v0.0.0-20210724152146-4ad1a8207f65/go.mod h1:5R2h2EEX+qri8jOWMbJCtaPWkrrNc7OHwsp2TCqp7ak=
github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM=
github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg=
github.com/jackc/pgproto3 v1.1.0/go.mod h1:eR5FA3leWg7p9aeAqi37XOTgTIbkABlvcPB3E5rlc78=
github.com/jackc/pgproto3/v2 v2.0.0-alpha1.0.20190420180111-c116219b62db/go.mod h1:bhq50y+xrl9n5mRYyCBFKkpRVTLYJVWeCc+mEAI3yXA=
github.com/jackc/pgproto3/v2 v2.0.0-alpha1.0.20190609003834-432c2951c711/go.mod h1:uH0AWtUmuShn0bcesswc4aBTWGvw0cAxIJp+6OB//Wg=
github.com/jackc/pgproto3/v2 v2.0.0-rc3/go.mod h1:ryONWYqW6dqSg1Lw6vXNMXoBJhpzvWKnT95C46ckYeM=
github.com/jackc/pgproto3/v2 v2.0.0-rc3.0.20190831210041-4c03ce451f29/go.mod h1:ryONWYqW6dqSg1Lw6vXNMXoBJhpzvWKnT95C46ckYeM=
github.com/jackc/pgproto3/v2 v2.0.6/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA=
github.com/jackc/pgproto3/v2 v2.1.1/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA=
github.com/jackc/pgproto3/v2 v2.3.3 h1:1HLSx5H+tXR9pW3in3zaztoEwQYRC9SQaYUHjTSUOag=
github.com/jackc/pgproto3/v2 v2.3.3/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA=
github.com/jackc/pgservicefile v0.0.0-20200714003250-2b9c44734f2b/go.mod h1:vsD4gTJCa9TptPL8sPkXrLZ+hDuNrZCnj29CQpr4X1E=
github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a h1:bbPeKD0xmW/Y25WS6cokEszi5g+S0QxI/d45PkRi7Nk=
github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM=
github.com/jackc/pgtype v0.0.0-20190421001408-4ed0de4755e0/go.mod h1:hdSHsc1V01CGwFsrv11mJRHWJ6aifDLfdV3aVjFF0zg=
github.com/jackc/pgtype v0.0.0-20190824184912-ab885b375b90/go.mod h1:KcahbBH1nCMSo2DXpzsoWOAfFkdEtEJpPbVLq8eE+mc=
github.com/jackc/pgtype v0.0.0-20190828014616-a8802b16cc59/go.mod h1:MWlu30kVJrUS8lot6TQqcg7mtthZ9T0EoIBFiJcmcyw=
github.com/jackc/pgtype v1.8.1-0.20210724151600-32e20a603178/go.mod h1:C516IlIV9NKqfsMCXTdChteoXmwgUceqaLfjg2e3NlM=
github.com/jackc/pgtype v1.14.0 h1:y+xUdabmyMkJLyApYuPj38mW+aAIqCe5uuBB51rH3Vw=
github.com/jackc/pgtype v1.14.0/go.mod h1:LUMuVrfsFfdKGLw+AFFVv6KtHOFMwRgDDzBt76IqCA4=
github.com/jackc/pgx/v4 v4.0.0-20190420224344-cc3461e65d96/go.mod h1:mdxmSJJuR08CZQyj1PVQBHy9XOp5p8/SHH6a0psbY9Y=
github.com/jackc/pgx/v4 v4.0.0-20190421002000-1b8f0016e912/go.mod h1:no/Y67Jkk/9WuGR0JG/JseM9irFbnEPbuWV2EELPNuM=
github.com/jackc/pgx/v4 v4.0.0-pre1.0.20190824185557-6972a5742186/go.mod h1:X+GQnOEnf1dqHGpw7JmHqHc1NxDoalibchSk9/RWuDc=
github.com/jackc/pgx/v4 v4.12.1-0.20210724153913-640aa07df17c/go.mod h1:1QD0+tgSXP7iUjYm9C1NxKhny7lq6ee99u/z+IHFcgs=
github.com/jackc/pgx/v4 v4.18.3 h1:dE2/TrEsGX3RBprb3qryqSV9Y60iZN1C6i8IrmW9/BA=
github.com/jackc/pgx/v4 v4.18.3/go.mod h1:Ey4Oru5tH5sB6tV7hDmfWFahwF15Eb7DNXlRKx2CkVw=
github.com/jackc/puddle v0.0.0-20190413234325-e4ced69a3a2b/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=
github.com/jackc/puddle v0.0.0-20190608224051-11cab39313c9/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=
github.com/jackc/puddle v1.1.3/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=
github.com/jackc/puddle v1.3.0 h1:eHK/5clGOatcjX3oWGBO/MpxpbHzSwud5EWTSCI+MX0=
github.com/jackc/puddle v1.3.0/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=
github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9YPoQUg=
github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo=
github.com/jmespath/go-jmespath/internal/testify v1.5.1 h1:shLQSRRSCCPj3f2gpwzGwWFoC7ycTf1rcQZHOlsJ6N8=
github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U=
github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0=
github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4=
github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
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/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
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=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/pty v1.1.8/go.mod h1:O1sed60cT9XZ5uDucP5qwvh+TE3NnUj51EiZO/lmSfw=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc=
github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw=
github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
github.com/lib/pq v1.1.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
github.com/lib/pq v1.10.2/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw=
github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
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/mattn/go-colorable v0.1.1/go.mod h1:FuOcm+DKB9mbwrcAfNl7/TZVBZ6rcnceauSikq3lYCQ=
github.com/mattn/go-colorable v0.1.6/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
github.com/mattn/go-isatty v0.0.5/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
github.com/mattn/go-isatty v0.0.7/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
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=
github.com/moby/go-archive v0.1.0/go.mod h1:G9B+YoujNohJmrIYFBpSd54GTUB4lt9S+xVQvsJyFuo=
github.com/moby/patternmatcher v0.6.0 h1:GmP9lR19aU5GqSSFko+5pRqHi+Ohk1O69aFiKkVGiPk=
github.com/moby/patternmatcher v0.6.0/go.mod h1:hDPoyOpDY7OrrMDLaYoY3hf52gNCR/YOUYxkhApJIxc=
github.com/moby/sys/atomicwriter v0.1.0 h1:kw5D/EqkBwsBFi0ss9v1VG3wIkVhzGvLklJ+w3A14Sw=
github.com/moby/sys/atomicwriter v0.1.0/go.mod h1:Ul8oqv2ZMNHOceF643P6FKPXeCmYtlQMvpizfsSoaWs=
github.com/moby/sys/sequential v0.6.0 h1:qrx7XFUd/5DxtqcoH1h438hF5TmOvzC/lspjy7zgvCU=
github.com/moby/sys/sequential v0.6.0/go.mod h1:uyv8EUTrca5PnDsdMGXhZe6CCe8U/UiTWd+lL+7b/Ko=
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.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/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=
github.com/opencontainers/image-spec v1.1.1/go.mod h1:qpqAh3Dmcf36wStyyWU+kCeDgrGnAve2nCC8+7h8Q0M=
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
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/prometheus/client_golang v1.22.0 h1:rb93p9lokFEsctTys46VnV1kLCDpVZ0a/Y92Vm0Zc6Q=
github.com/prometheus/client_golang v1.22.0/go.mod h1:R7ljNsLXhuQXYZYtw6GAE9AZg8Y7vEW5scdCXrWRXC0=
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/prometheus/client_golang v1.23.0 h1:ust4zpdl9r4trLY/gSjlm07PuiBq2ynaXXlptpfy8Uc=
github.com/prometheus/client_golang v1.23.0/go.mod h1:i/o0R9ByOnHX0McrTMTyhYvKE4haaf2mW08I+jGAjEE=
github.com/prometheus/client_model v0.6.2 h1:oBsgwpGs7iVziMvrGhE53c/GrLUsZdHnqNwqPLxwZyk=
github.com/prometheus/client_model v0.6.2/go.mod h1:y3m2F6Gdpfy6Ut/GBsUqTWZqCUvMVzSfMLjcu6wAwpE=
github.com/prometheus/common v0.65.0 h1:QDwzd+G1twt//Kwj/Ww6E9FQq1iVMmODnILtW1t2VzE=
github.com/prometheus/common v0.65.0/go.mod h1:0gZns+BLRQ3V6NdaerOhMbwwRbNh9hkGINtQAsP5GS8=
github.com/prometheus/procfs v0.17.0 h1:FuLQ+05u4ZI+SS/w9+BWEM2TXiHKsUQ9TADiRH7DuK0=
github.com/prometheus/procfs v0.17.0/go.mod h1:oPQLaDAMRbA+u8H5Pbfq+dl3VDAvHxMUOVhe0wYB2zw=
github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ=
github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog=
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
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/rs/xid v1.2.1/go.mod h1:+uKXf+4Djp6Md1KODXJxgGQPKngRmWyn10oCKFzNHOQ=
github.com/rs/zerolog v1.13.0/go.mod h1:YbFCdg8HfsridGWAh22vktObvhZbQsZXe4/zB0OKkWU=
github.com/rs/zerolog v1.15.0/go.mod h1:xYTKnLHcpfU2225ny5qZjxnj9NvkumZYjJHlAThCjNc=
github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0=
github.com/sergi/go-diff v1.3.1 h1:xkr+Oxo4BOQKmkn/B9eMK0g5Kg/983T9DqqPHwYqD+8=
github.com/sergi/go-diff v1.3.1/go.mod h1:aMJSSKb2lpPvRNec0+w3fl7LP9IOFzdc9Pa4NFbPK1I=
github.com/shirou/gopsutil/v4 v4.25.5 h1:rtd9piuSMGeU8g1RMXjZs9y9luK5BwtnG7dZaQUJAsc=
github.com/shirou/gopsutil/v4 v4.25.5/go.mod h1:PfybzyydfZcN+JMMjkF6Zb8Mq1A/VcogFFg7hj50W9c=
github.com/shopspring/decimal v0.0.0-20180709203117-cd690d0c9e24/go.mod h1:M+9NzErvs504Cn4c5DxATwIqPbtswREoFCre64PpcG4=
github.com/shopspring/decimal v1.2.0 h1:abSATXmQEYyShuxI4/vyW3tV1MrKAJzCZ/0zLUXYbsQ=
github.com/shopspring/decimal v1.2.0/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o=
github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q=
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ=
github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
github.com/sosodev/duration v1.3.1 h1:qtHBDMQ6lvMQsL15g4aopM4HEfOaYuhWBw3NPTtlqq4=
github.com/sosodev/duration v1.3.1/go.mod h1:RQIBBX0+fMLc/D9+Jb/fwvVmo0eZvDDEERAikUR6SDg=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE=
github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY=
github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/testcontainers/testcontainers-go v0.38.0 h1:d7uEapLcv2P8AvH8ahLqDMMxda2W9gQN1nRbHS28HBw=
github.com/testcontainers/testcontainers-go v0.38.0/go.mod h1:C52c9MoHpWO+C4aqmgSU+hxlR5jlEayWtgYrb8Pzz1w=
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/vektah/gqlparser/v2 v2.5.30 h1:EqLwGAFLIzt1wpx1IPpY67DwUujF1OfzgEyDsLrN6kE=
github.com/vektah/gqlparser/v2 v2.5.30/go.mod h1:D1/VCZtV3LPnQrcPBeR/q5jkSQIPti0uYCP/RI0gIeo=
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yusufpapurcu/wmi v1.2.4 h1:zFUKzehAFReQwLys1b/iSMl+JQGSCSjtVqQn9bBrPo0=
github.com/yusufpapurcu/wmi v1.2.4/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0=
github.com/zenazn/goji v0.9.0/go.mod h1:7S9M489iMyHBNxwZnk9/EHS098H4/F6TATF2mIxtB1Q=
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/otel v1.36.0 h1:UumtzIklRBY6cI/lllNZlALOF5nNIzJVb16APdvgTXg=
go.opentelemetry.io/otel v1.36.0/go.mod h1:/TcFMXYjyRNh8khOAO9ybYkqaDBb/70aVwkNML4pP8E=
go.opentelemetry.io/otel/metric v1.36.0 h1:MoWPKVhQvJ+eeXWHFBOPoBOi20jh6Iq2CcCREuTYufE=
go.opentelemetry.io/otel/metric v1.36.0/go.mod h1:zC7Ks+yeyJt4xig9DEw9kuUFe5C3zLbVjV2PzT6qzbs=
go.opentelemetry.io/otel/sdk v1.36.0 h1:b6SYIuLRs88ztox4EyrvRti80uXIFy+Sqzoh9kFULbs=
go.opentelemetry.io/otel/sdk v1.36.0/go.mod h1:+lC+mTgD+MUWfjJubi2vvXWcVxyr9rmlshZni72pXeY=
go.opentelemetry.io/otel/sdk/metric v1.36.0 h1:r0ntwwGosWGaa0CrSt8cuNuTcccMXERFwHX4dThiPis=
go.opentelemetry.io/otel/sdk/metric v1.36.0/go.mod h1:qTNOhFDfKRwX0yXOqJYegL5WRaW376QbB7P4Pb0qva4=
go.opentelemetry.io/otel/trace v1.36.0 h1:ahxWNuqZjpdiFAyrIoQ4GIiAIhxAunQR6MUoKrsNd4w=
go.opentelemetry.io/otel/trace v1.36.0/go.mod h1:gQ+OnDZzrybY4k4seLzPAWNwVBBVlF2szhehOBB/tGA=
golang.org/x/crypto v0.40.0 h1:r4x+VvoG5Fm+eJcxMaY8CQM7Lb0l1lsmjGBQ6s8BfKM=
golang.org/x/crypto v0.40.0/go.mod h1:Qr1vMER5WyS2dfPHAlsOj01wgLbsyWtFn/aY+5+ZdxY=
golang.org/x/net v0.42.0 h1:jzkYrhi3YQWD6MLBJcsklgQsoAcw89EcZbJw8Z614hs=
golang.org/x/net v0.42.0/go.mod h1:FF1RA5d3u7nAYA4z2TkclSCKh68eSXtiFwcWQpPXdt8=
golang.org/x/sys v0.34.0 h1:H5Y5sJ2L2JRdyv7ROF1he/lPdvFsd0mJHFw2ThKHxLA=
golang.org/x/sys v0.34.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
golang.org/x/text v0.27.0 h1:4fGWRpyh641NLlecmyl4LOe6yDdfaYNrGb2zdfo4JV4=
golang.org/x/text v0.27.0/go.mod h1:1D28KMCvyooCX9hBiosv5Tz/+YLxj0j7XhWjpSUF7CU=
google.golang.org/genproto/googleapis/rpc v0.0.0-20250721164621-a45f3dfb1074 h1:qJW29YvkiJmXOYMu5Tf8lyrTp3dOS+K4z6IixtLaCf8=
google.golang.org/genproto/googleapis/rpc v0.0.0-20250721164621-a45f3dfb1074/go.mod h1:qQ0YXyHHx3XkvlzUtpXDkS29lDSafHMZBAZDc03LQ3A=
google.golang.org/grpc v1.74.2 h1:WoosgB65DlWVC9FqI82dGsZhWFNBSLjQ84bjROOpMu4=
google.golang.org/grpc v1.74.2/go.mod h1:CtQ+BGjaAIXHs/5YS3i473GqwBBa1zGQNevxdeBEXrM=
google.golang.org/protobuf v1.36.6 h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9xY=
google.golang.org/protobuf v1.36.6/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY=
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/otlp/otlptrace v1.37.0 h1:Ahq7pZmv87yiyn3jeFz/LekZmPLLdKejuO3NcK9MssM=
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.37.0/go.mod h1:MJTqhM0im3mRLw1i8uGHnCvUEeS7VwRyxlLC78PA18M=
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.19.0 h1:IeMeyr1aBvBiPVYihXIaeIZba6b8E1bYp7lbdxK8CQg=
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.19.0/go.mod h1:oVdCUtjq9MK9BlS7TtucsQwUcXcymNiEDjgDD2jMtZU=
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.7.1 h1:gTOMpGDb0WTBOP8JaO72iL3auEZhVmAQg4ipjOVAtj4=
go.opentelemetry.io/proto/otlp v1.7.1/go.mod h1:b2rVh6rfI/s2pHWNlB7ILJcRALpcNDzKhACevjI+ZnE=
go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
go.uber.org/atomic v1.5.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ=
go.uber.org/atomic v1.6.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ=
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0=
go.uber.org/multierr v1.3.0/go.mod h1:VgVr7evmIr6uPjLBxg28wmKNXyqE9akIJ5XnfpiKl+4=
go.uber.org/multierr v1.5.0/go.mod h1:FeouvMocqHpRaaGuG9EjoKcStLC43Zu/fmqdUMPcKYU=
go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee/go.mod h1:vJERXedbb3MVM5f9Ejo0C68/HhF8uaILCdgjnY+goOA=
go.uber.org/zap v1.9.1/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
go.uber.org/zap v1.13.0/go.mod h1:zwrFLgMcdUuIBviXEYEH1YKNaOBnKXsx2IPda5bBwHM=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20190411191339-88737f569e3a/go.mod h1:WFFai1msRO1wXaEeE5yQxYXgSfI8pQAWXbQop6sCtWE=
golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20201203163018-be400aefbc4c/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I=
golang.org/x/crypto v0.0.0-20210616213533-5ff15b29337e/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.41.0 h1:WKYxWedPGCTVVl5+WHSSrOBT0O8lx32+zxmHxijgXp4=
golang.org/x/crypto v0.41.0/go.mod h1:pO5AFd7FA68rFak7rOAGVuygIISepHftHnr8dr6+sUc=
golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc=
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.43.0 h1:lat02VYK2j4aLzMzecihNvTlJNQUq316m2Mr9rnM6YE=
golang.org/x/net v0.43.0/go.mod h1:vhO1fvI4dGsIjh73sWfUVjj3N7CA9WkKJNQm2svM6Jg=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190403152447-81d4e9dc473e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201204225414-ed752295db88/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.35.0 h1:vz1N37gP5bs89s7He8XuIYXpyY0+QlsKmzipCbUtyxI=
golang.org/x/sys v0.35.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.34.0 h1:O/2T7POpk0ZZ7MAzMeWFSg6S5IpWd/RXDlM9hgM3DR4=
golang.org/x/term v0.34.0/go.mod h1:5jC53AEywhIVebHgPVeg0mj8OD3VO9OzclacVrqpaAw=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
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.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.28.0 h1:rhazDwis8INMIwQ4tpjLDzUhx6RlXqZNPEM0huQojng=
golang.org/x/text v0.28.0/go.mod h1:U8nCwOR8jO/marOQ0QbDiOngZVEBB7MAiitBuMjXiNU=
golang.org/x/time v0.0.0-20220210224613-90d013bbcef8 h1:vVKdlvoWBphwdxWKrFZEuM0kGgGLxUOYcY4U/2Vjg44=
golang.org/x/time v0.0.0-20220210224613-90d013bbcef8/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190425163242-31fd60d6bfdc/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
golang.org/x/tools v0.0.0-20190823170909-c4a336ef6a2f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191029041327-9cc4af7d6b2c/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20200103221440-774c71fcf114/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/xerrors v0.0.0-20190410155217-1f06c39b4373/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20190513163551-3ee3066db522/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/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-20250728155136-f173205681a0 h1:0UOBWO4dC+e51ui0NFKSPbkHHiQ4TmrEfEZMLDyRmY8=
google.golang.org/genproto/googleapis/api v0.0.0-20250728155136-f173205681a0/go.mod h1:8ytArBbtOy2xfht+y2fqKd5DRDJRUQhqbyEnQ4bDChs=
google.golang.org/genproto/googleapis/rpc v0.0.0-20250818200422-3122310a409c h1:qXWI/sQtv5UKboZ/zUk7h+mrf/lXORyI+n9DKDAusdg=
google.golang.org/genproto/googleapis/rpc v0.0.0-20250818200422-3122310a409c/go.mod h1:gw1tLEfykwDz2ET4a12jcXt4couGAm7IwsVaTy0Sflo=
google.golang.org/grpc v1.75.0 h1:+TW+dqTd2Biwe6KKfhE5JpiYIBWq865PhKGSXiivqt4=
google.golang.org/grpc v1.75.0/go.mod h1:JtPAzKiq4v1xcAB2hydNlWI2RnF85XXcV0mhKXr2ecQ=
google.golang.org/protobuf v1.36.8 h1:xHScyCOEuuwZEc6UtSOvPbAT4zRh0xcNRYekJwfqyMc=
google.golang.org/protobuf v1.36.8/go.mod h1:fuxRtAxBytpl4zzqUh6/eyUujkJdNiuEkXntxiD/uRU=
gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc h1:2gGKlE2+asNV9m7xrywl36YYNnBG5ZQ0r/BOOxqPpmk=
gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc/go.mod h1:m7x9LTH6d71AHyAX77c9yqWCCa3UKHcVEj9y7hAtKDk=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
gopkg.in/gomail.v2 v2.0.0-20160411212932-81ebce5c23df h1:n7WqCuqOuCbNr617RXOY0AWRXxgwEyPp2z+p0+hgMuE=
gopkg.in/gomail.v2 v2.0.0-20160411212932-81ebce5c23df/go.mod h1:LRQQ+SO6ZHR7tOkpBDuZnXENFzX8qRjMDMyPD6BRkCw=
gopkg.in/inconshreveable/log15.v2 v2.0.0-20180818164646-67afb5ed74ec/go.mod h1:aPpfJ7XW+gOuirDoZ8gHhLh3kZ1B08FtV2bbmy7Jv3s=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gotest.tools/v3 v3.5.2 h1:7koQfIKdy+I8UTetycgUqXWSDwpgv193Ka+qRsmBY8Q=
gotest.tools/v3 v3.5.2/go.mod h1:LtdLGcnqToBH83WByAAi/wiwSFCArdFIUV/xxN4pcjA=
honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=

View File

@ -1,9 +1,10 @@
package config
import (
"time"
"github.com/caarlos0/env/v8"
"github.com/joho/godotenv"
"time"
)
type Config struct {
@ -29,7 +30,8 @@ type Config struct {
URL string `env:"AppURL,required"`
}
GRPC struct {
MessageServiceAddress string `env:"MESSAGE_SERVICE_ADDRESS"`
MessageServiceAddress string `env:"MESSAGE_SERVICE_ADDRESS"`
SubscribeServiceAddress string `env:"SUBSCRIBE_SERVICE_ADDRESS"`
}
}

File diff suppressed because it is too large Load Diff

View File

@ -5,7 +5,6 @@ import (
"fmt"
"log"
"tailly_back_v2/internal/domain"
"tailly_back_v2/internal/http/middleware"
"tailly_back_v2/proto"
"time"
)
@ -19,17 +18,6 @@ type subscriptionResolver struct{ *Resolver }
// CreateChat is the resolver for the createChat field.
func (r *mutationResolver) CreateChat(ctx context.Context, user1Id int, user2Id int) (*domain.Chat, error) {
start := time.Now()
success := false
defer func() {
duration := time.Since(start)
middleware.IncGQLOperation("mutation", "createChat", success, duration)
if !success {
middleware.IncError("gql_operation", "createChat", "error")
}
}()
// Просто вызываем gRPC метод, вся логика уже там
res, err := r.MessageClient.CreateChat(ctx, &proto.CreateChatRequest{
User1Id: int32(user1Id),
@ -39,24 +27,12 @@ func (r *mutationResolver) CreateChat(ctx context.Context, user1Id int, user2Id
return nil, fmt.Errorf("failed to create chat: %w", err)
}
success = true
// Преобразуем proto-чат в domain-модель
return protoChatToDomain(res.Chat), nil
}
// SendMessage реализация мутации для отправки сообщения
func (r *mutationResolver) SendMessage(ctx context.Context, chatID int, content string) (*domain.Message, error) {
start := time.Now()
success := false
defer func() {
duration := time.Since(start)
middleware.IncGQLOperation("mutation", "sendMessage", success, duration)
if !success {
middleware.IncError("gql_operation", "sendMessage", "error")
}
}()
// Получаем senderID из контекста
senderID, err := getUserIDFromContext(ctx)
if err != nil {
@ -71,25 +47,11 @@ func (r *mutationResolver) SendMessage(ctx context.Context, chatID int, content
if err != nil {
return nil, fmt.Errorf("failed to send message: %w", err)
}
success = true
middleware.IncWebSocketMessage("outgoing", "chat_message")
return protoMessageToDomain(res.Message), nil
}
// UpdateMessageStatus реализация мутации для обновления статуса сообщения
func (r *mutationResolver) UpdateMessageStatus(ctx context.Context, messageID int, status MessageStatus) (*domain.Message, error) {
start := time.Now()
success := false
defer func() {
duration := time.Since(start)
middleware.IncGQLOperation("mutation", "updateMessageStatus", success, duration)
if !success {
middleware.IncError("gql_operation", "updateMessageStatus", "error")
}
}()
var statusStr string
switch status {
case MessageStatusSent:
@ -110,23 +72,11 @@ func (r *mutationResolver) UpdateMessageStatus(ctx context.Context, messageID in
return nil, fmt.Errorf("failed to update message status: %w", err)
}
success = true
return protoMessageToDomain(res.Message), nil
}
// GetChat реализация запроса для получения чата
func (r *queryResolver) GetChat(ctx context.Context, user1Id int, user2Id int) (*domain.Chat, error) {
start := time.Now()
success := false
defer func() {
duration := time.Since(start)
middleware.IncGQLOperation("query", "getChat", success, duration)
if !success {
middleware.IncError("gql_operation", "getChat", "error")
}
}()
res, err := r.MessageClient.GetChat(ctx, &proto.GetChatRequest{
User1Id: int32(user1Id),
User2Id: int32(user2Id),
@ -134,24 +84,11 @@ func (r *queryResolver) GetChat(ctx context.Context, user1Id int, user2Id int) (
if err != nil {
return nil, fmt.Errorf("failed to get chat: %w", err)
}
success = true
return protoChatToDomain(res.Chat), nil
}
// GetChatMessages реализация запроса для получения сообщений чата
func (r *queryResolver) GetChatMessages(ctx context.Context, chatID int, limit int, offset int) ([]*domain.Message, error) {
start := time.Now()
success := false
defer func() {
duration := time.Since(start)
middleware.IncGQLOperation("query", "getChatMessages", success, duration)
if !success {
middleware.IncError("gql_operation", "getChatMessages", "error")
}
}()
res, err := r.MessageClient.GetChatMessages(ctx, &proto.GetChatMessagesRequest{
ChatId: int32(chatID),
Limit: int32(limit),
@ -160,58 +97,30 @@ func (r *queryResolver) GetChatMessages(ctx context.Context, chatID int, limit i
if err != nil {
return nil, fmt.Errorf("failed to get chat messages: %w", err)
}
var messages []*domain.Message
for _, msg := range res.Messages {
messages = append(messages, protoMessageToDomain(msg))
}
success = true
return messages, nil
}
// GetUserChats реализация запроса для получения чатов пользователя
func (r *queryResolver) GetUserChats(ctx context.Context, userID int) ([]*domain.Chat, error) {
start := time.Now()
success := false
defer func() {
duration := time.Since(start)
middleware.IncGQLOperation("query", "getUserChats", success, duration)
if !success {
middleware.IncError("gql_operation", "getUserChats", "error")
}
}()
res, err := r.MessageClient.GetUserChats(ctx, &proto.GetUserChatsRequest{
UserId: int32(userID),
})
if err != nil {
return nil, fmt.Errorf("failed to get user chats: %w", err)
}
var chats []*domain.Chat
for _, chat := range res.Chats {
chats = append(chats, protoChatToDomain(chat))
}
success = true
return chats, nil
}
// MessageStream реализация подписки на новые сообщения
func (r *subscriptionResolver) MessageStream(ctx context.Context, userID int) (<-chan *domain.Message, error) {
start := time.Now()
success := false
defer func() {
duration := time.Since(start)
middleware.IncGQLOperation("subscription", "messageStream", success, duration)
if !success {
middleware.IncError("gql_operation", "messageStream", "error")
}
}()
messageChan := make(chan *domain.Message, 100)
go func() {
@ -227,14 +136,12 @@ func (r *subscriptionResolver) MessageStream(ctx context.Context, userID int) (<
err := r.runMessageStream(ctx, userID, messageChan)
if err != nil {
log.Printf("MessageStream error: %v, reconnecting...", err)
middleware.IncError("websocket_stream", "messageStream", "warning")
}
time.Sleep(2 * time.Second) // Задержка перед переподключением
}
}
}()
success = true
return messageChan, nil
}
@ -248,7 +155,6 @@ func (r *subscriptionResolver) runMessageStream(ctx context.Context, userID int,
})
if err != nil {
log.Printf("Failed to mark messages as delivered: %v", err)
middleware.IncError("message_status", "updateStatus", "warning")
}
streamCtx, cancel := context.WithCancel(ctx)
@ -269,7 +175,6 @@ func (r *subscriptionResolver) runMessageStream(ctx context.Context, userID int,
case <-heartbeat.C:
// Отправляем ping для поддержания соединения
if err := stream.Context().Err(); err != nil {
middleware.IncError("websocket_heartbeat", "messageStream", "error")
return fmt.Errorf("connection lost: %w", err)
}
case <-ctx.Done():
@ -277,7 +182,6 @@ func (r *subscriptionResolver) runMessageStream(ctx context.Context, userID int,
default:
msg, err := stream.Recv()
if err != nil {
middleware.IncError("websocket_receive", "messageStream", "error")
return fmt.Errorf("receive error: %w", err)
}
@ -285,7 +189,6 @@ func (r *subscriptionResolver) runMessageStream(ctx context.Context, userID int,
select {
case messageChan <- protoMessageToDomain(msg.Message):
log.Printf("Delivered message %d to user %d", msg.Message.Id, userID)
middleware.IncWebSocketMessage("incoming", "chat_message")
case <-ctx.Done():
return nil
}

View File

@ -9,15 +9,70 @@ import (
"strconv"
)
type FollowResult struct {
Success bool `json:"success"`
Message string `json:"message"`
}
type Follower struct {
UserID int `json:"userId"`
Username string `json:"username"`
Avatar string `json:"avatar"`
CreatedAt string `json:"createdAt"`
}
type FollowersResponse struct {
Followers []*Follower `json:"followers"`
TotalCount int `json:"totalCount"`
}
type Following struct {
UserID int `json:"userId"`
Username string `json:"username"`
Avatar string `json:"avatar"`
CreatedAt string `json:"createdAt"`
}
type FollowingResponse struct {
Following []*Following `json:"following"`
TotalCount int `json:"totalCount"`
}
type MarkNotificationReadResult struct {
Success bool `json:"success"`
Message string `json:"message"`
}
type Mutation struct {
}
type NotificationsResponse struct {
Notifications []*SubscriptionNotification `json:"notifications"`
TotalCount int `json:"totalCount"`
UnreadCount int `json:"unreadCount"`
}
type Query struct {
}
type Subscription struct {
}
type SubscriptionNotification struct {
ID int `json:"id"`
FollowerID int `json:"followerId"`
FollowerUsername string `json:"followerUsername"`
FollowerAvatar string `json:"followerAvatar"`
IsRead bool `json:"isRead"`
CreatedAt string `json:"createdAt"`
ExpiresAt string `json:"expiresAt"`
}
type UnfollowResult struct {
Success bool `json:"success"`
Message string `json:"message"`
}
type MessageStatus string
const (

View File

@ -15,20 +15,23 @@ import (
// It serves as dependency injection for your app, add any dependencies you require here.
type Resolver struct {
Services *service.Services
DeviceRepo repository.DeviceRepository // Добавляем репозиторий устройств напрямую
MessageClient proto.MessageServiceClient
Services *service.Services
DeviceRepo repository.DeviceRepository // Добавляем репозиторий устройств напрямую
MessageClient proto.MessageServiceClient
SubscribeClient proto.SubscribeServiceClient
}
func NewResolver(
services *service.Services,
db *sql.DB, // Принимаем подключение к БД
messageClient proto.MessageServiceClient,
subscribeClient proto.SubscribeServiceClient,
) *Resolver {
return &Resolver{
Services: services,
DeviceRepo: repository.NewDeviceRepository(db),
MessageClient: messageClient,
Services: services,
DeviceRepo: repository.NewDeviceRepository(db),
MessageClient: messageClient,
SubscribeClient: subscribeClient,
}
}

View File

@ -7,6 +7,16 @@ type User {
emailConfirmedAt: String # Дата подтверждения email (может быть null)
createdAt: String! # Дата создания
updatedAt: String! # Дата обновления
followersCount: Int!
followingCount: Int!
isFollowing(userId: Int!): Boolean!
followers(limit: Int = 20, offset: Int = 0): FollowersResponse!
following(limit: Int = 20, offset: Int = 0): FollowingResponse!
subscriptionNotifications(
unreadOnly: Boolean = false
limit: Int = 20
offset: Int = 0
): NotificationsResponse!
}
# Пост в блоге
@ -104,6 +114,62 @@ type Device {
lastActiveAt: String!
}
# типы для подписок
type Follower {
userId: Int!
username: String!
avatar: String!
createdAt: String!
}
type Following {
userId: Int!
username: String!
avatar: String!
createdAt: String!
}
type SubscriptionNotification {
id: Int!
followerId: Int!
followerUsername: String!
followerAvatar: String!
isRead: Boolean!
createdAt: String!
expiresAt: String!
}
type FollowersResponse {
followers: [Follower!]!
totalCount: Int!
}
type FollowingResponse {
following: [Following!]!
totalCount: Int!
}
type NotificationsResponse {
notifications: [SubscriptionNotification!]!
totalCount: Int!
unreadCount: Int!
}
type FollowResult {
success: Boolean!
message: String!
}
type UnfollowResult {
success: Boolean!
message: String!
}
type MarkNotificationReadResult {
success: Boolean!
message: String!
}
# Запросы (получение данных)
type Query {
me: User! # Получить текущего пользователя
@ -117,6 +183,27 @@ type Query {
getUserChats(userId: Int!): [Chat!]!
mySessions: [Session!]!
comments(postID: Int!): [Comment!]!
# Получить количество подписчиков
getFollowersCount(userId: Int!): Int!
# Получить количество подписок
getFollowingCount(userId: Int!): Int!
# Проверить подписку
isFollowing(followingId: Int!): Boolean!
# Получить список подписчиков
getFollowers(userId: Int!, limit: Int = 20, offset: Int = 0): FollowersResponse!
# Получить список подписок
getFollowing(userId: Int!, limit: Int = 20, offset: Int = 0): FollowingResponse!
# Получить уведомления о подписках
getSubscriptionNotifications(
unreadOnly: Boolean = false
limit: Int = 20
offset: Int = 0
): NotificationsResponse!
}
# Мутации (изменение данных)
@ -155,6 +242,9 @@ type Mutation {
# Повторная отправка подтверждения email
resendEmailConfirmation: Boolean!
deletePost(id: Int!): Boolean!
followUser(followingId: Int!): FollowResult!
unfollowUser(followingId: Int!): UnfollowResult!
markNotificationAsRead(notificationId: Int!): MarkNotificationReadResult!
}
type Subscription {

View File

@ -0,0 +1,240 @@
package graph
import (
"context"
"fmt"
"tailly_back_v2/proto"
)
// FollowUser is the resolver for the followUser field.
func (r *mutationResolver) FollowUser(ctx context.Context, followingID int) (*FollowResult, error) {
followerID, err := getUserIDFromContext(ctx)
if err != nil {
return nil, fmt.Errorf("authentication required: %w", err)
}
res, err := r.SubscribeClient.FollowUser(ctx, &proto.FollowRequest{
FollowerId: int32(followerID),
FollowingId: int32(followingID),
})
if err != nil {
return nil, fmt.Errorf("failed to follow user: %w", err)
}
return &FollowResult{Success: res.Success, Message: res.Message}, nil
}
// UnfollowUser is the resolver for the unfollowUser field.
func (r *mutationResolver) UnfollowUser(ctx context.Context, followingID int) (*UnfollowResult, error) {
followerID, err := getUserIDFromContext(ctx)
if err != nil {
return nil, fmt.Errorf("authentication required: %w", err)
}
res, err := r.SubscribeClient.UnfollowUser(ctx, &proto.UnfollowRequest{
FollowerId: int32(followerID),
FollowingId: int32(followingID),
})
if err != nil {
return nil, fmt.Errorf("failed to unfollow user: %w", err)
}
return &UnfollowResult{Success: res.Success, Message: res.Message}, nil
}
// MarkNotificationAsRead is the resolver for the markNotificationAsRead field.
func (r *mutationResolver) MarkNotificationAsRead(ctx context.Context, notificationID int) (*MarkNotificationReadResult, error) {
userID, err := getUserIDFromContext(ctx)
if err != nil {
return nil, fmt.Errorf("authentication required: %w", err)
}
res, err := r.SubscribeClient.MarkNotificationAsRead(ctx, &proto.MarkNotificationReadRequest{
UserId: int32(userID),
NotificationId: int32(notificationID),
})
if err != nil {
return nil, fmt.Errorf("failed to unfollow user: %w", err)
}
return &MarkNotificationReadResult{Success: res.Success, Message: res.Message}, nil
}
// GetFollowersCount is the resolver for the getFollowersCount field.
func (r *queryResolver) GetFollowersCount(ctx context.Context, userID int) (int, error) {
res, err := r.SubscribeClient.GetFollowersCount(ctx, &proto.GetCountRequest{
UserId: int32(userID),
})
if err != nil {
return 0, fmt.Errorf("failed to get followers count: %w", err)
}
return int(res.Count), nil
}
// GetFollowingCount is the resolver for the getFollowingCount field.
func (r *queryResolver) GetFollowingCount(ctx context.Context, userID int) (int, error) {
res, err := r.SubscribeClient.GetFollowingCount(ctx, &proto.GetCountRequest{
UserId: int32(userID),
})
if err != nil {
return 0, fmt.Errorf("failed to get following count: %w", err)
}
return int(res.Count), nil
}
// IsFollowing is the resolver for the isFollowing field.
func (r *queryResolver) IsFollowing(ctx context.Context, followingID int) (bool, error) {
followerID, err := getUserIDFromContext(ctx)
if err != nil {
return false, fmt.Errorf("authentication required: %w", err)
}
res, err := r.SubscribeClient.IsFollowing(ctx, &proto.IsFollowingRequest{
FollowerId: int32(followerID),
FollowingId: int32(followingID),
})
if err != nil {
return false, fmt.Errorf("failed to check subscription status: %w", err)
}
return res.IsFollowing, nil
}
// GetFollowers is the resolver for the getFollowers field.
func (r *queryResolver) GetFollowers(ctx context.Context, userID int, limit *int, offset *int) (*FollowersResponse, error) {
limitVal := 20
if limit != nil {
limitVal = *limit
}
offsetVal := 0
if offset != nil {
offsetVal = *offset
}
res, err := r.SubscribeClient.GetFollowers(ctx, &proto.GetFollowersRequest{
UserId: int32(userID),
Limit: int32(limitVal),
Offset: int32(offsetVal),
})
if err != nil {
return nil, fmt.Errorf("failed to get followers: %w", err)
}
return &FollowersResponse{
Followers: convertProtoFollowersToGraphQL(res.Followers),
TotalCount: int(res.TotalCount),
}, nil
}
// Вспомогательная функция для преобразования
func convertProtoFollowersToGraphQL(protoFollowers []*proto.GetFollowersResponse_Follower) []*Follower {
var followers []*Follower
for _, pf := range protoFollowers {
followers = append(followers, &Follower{
UserID: int(pf.UserId),
Username: pf.Username,
Avatar: pf.Avatar,
})
}
return followers
}
// GetFollowing is the resolver for the getFollowing field.
func (r *queryResolver) GetFollowing(ctx context.Context, userID int, limit *int, offset *int) (*FollowingResponse, error) {
limitVal := 20
if limit != nil {
limitVal = *limit
}
offsetVal := 0
if offset != nil {
offsetVal = *offset
}
res, err := r.SubscribeClient.GetFollowing(ctx, &proto.GetFollowingRequest{
UserId: int32(userID),
Limit: int32(limitVal),
Offset: int32(offsetVal),
})
if err != nil {
return nil, fmt.Errorf("failed to get following: %w", err)
}
return &FollowingResponse{
Following: convertProtoFollowingToGraphQL(res.Following),
TotalCount: int(res.TotalCount),
}, nil
}
// Вспомогательная функция для преобразования
func convertProtoFollowingToGraphQL(protoFollowing []*proto.GetFollowingResponse_Following) []*Following {
var following []*Following
for _, pf := range protoFollowing {
following = append(following, &Following{
UserID: int(pf.UserId),
Username: pf.Username,
Avatar: pf.Avatar,
})
}
return following
}
// GetSubscriptionNotifications is the resolver for the getSubscriptionNotifications field.
func (r *queryResolver) GetSubscriptionNotifications(ctx context.Context, unreadOnly *bool, limit *int, offset *int) (*NotificationsResponse, error) {
userID, err := getUserIDFromContext(ctx)
if err != nil {
return nil, fmt.Errorf("authentication required: %w", err)
}
unreadOnlyVal := false
if unreadOnly != nil {
unreadOnlyVal = *unreadOnly
}
limitVal := 20
if limit != nil {
limitVal = *limit
}
offsetVal := 0
if offset != nil {
offsetVal = *offset
}
res, err := r.SubscribeClient.GetSubscriptionNotifications(ctx, &proto.GetNotificationsRequest{
UserId: int32(userID),
UnreadOnly: unreadOnlyVal,
Limit: int32(limitVal),
Offset: int32(offsetVal),
})
if err != nil {
return nil, fmt.Errorf("failed to get notifications: %w", err)
}
return &NotificationsResponse{
Notifications: convertProtoNotificationsToGraphQL(res.Notifications),
TotalCount: int(res.TotalCount),
UnreadCount: int(res.UnreadCount),
}, nil
}
func convertProtoNotificationsToGraphQL(protoNotifs []*proto.GetNotificationsResponse_Notification) []*SubscriptionNotification {
var notifications []*SubscriptionNotification
for _, pn := range protoNotifs {
notifications = append(notifications, &SubscriptionNotification{
ID: int(pn.Id),
FollowerID: int(pn.FollowerId),
FollowerUsername: pn.FollowerUsername,
FollowerAvatar: pn.FollowerAvatar,
IsRead: pn.IsRead,
CreatedAt: pn.CreatedAt,
ExpiresAt: pn.ExpiresAt,
})
}
return notifications
}

View File

@ -6,6 +6,7 @@ import (
"fmt"
"tailly_back_v2/internal/domain"
"tailly_back_v2/internal/service"
"tailly_back_v2/proto"
"time"
)
@ -118,3 +119,140 @@ func (r *queryResolver) Users(ctx context.Context) ([]*domain.User, error) {
}
return users, nil
}
// FollowersCount is the resolver for the followersCount field.
func (r *userResolver) FollowersCount(ctx context.Context, obj *domain.User) (int, error) {
res, err := r.SubscribeClient.GetFollowersCount(ctx, &proto.GetCountRequest{
UserId: int32(obj.ID),
})
if err != nil {
return 0, fmt.Errorf("failed to get followers count: %w", err)
}
return int(res.Count), nil
}
// FollowingCount is the resolver for the followingCount field.
func (r *userResolver) FollowingCount(ctx context.Context, obj *domain.User) (int, error) {
res, err := r.SubscribeClient.GetFollowingCount(ctx, &proto.GetCountRequest{
UserId: int32(obj.ID),
})
if err != nil {
return 0, fmt.Errorf("failed to get following count: %w", err)
}
return int(res.Count), nil
}
// IsFollowing is the resolver for the isFollowing field.
func (r *userResolver) IsFollowing(ctx context.Context, obj *domain.User, userID int) (bool, error) {
currentUserID, err := getUserIDFromContext(ctx)
if err != nil {
return false, fmt.Errorf("authentication required: %w", err)
}
res, err := r.SubscribeClient.IsFollowing(ctx, &proto.IsFollowingRequest{
FollowerId: int32(currentUserID),
FollowingId: int32(userID),
})
if err != nil {
return false, fmt.Errorf("failed to check subscription status: %w", err)
}
return res.IsFollowing, nil
}
// Followers is the resolver for the followers field.
func (r *userResolver) Followers(ctx context.Context, obj *domain.User, limit *int, offset *int) (*FollowersResponse, error) {
limitVal := 20
if limit != nil {
limitVal = *limit
}
offsetVal := 0
if offset != nil {
offsetVal = *offset
}
res, err := r.SubscribeClient.GetFollowers(ctx, &proto.GetFollowersRequest{
UserId: int32(obj.ID),
Limit: int32(limitVal),
Offset: int32(offsetVal),
})
if err != nil {
return nil, fmt.Errorf("failed to get followers: %w", err)
}
return &FollowersResponse{
Followers: convertProtoFollowersToGraphQL(res.Followers),
TotalCount: int(res.TotalCount),
}, nil
}
// Following is the resolver for the following field.
func (r *userResolver) Following(ctx context.Context, obj *domain.User, limit *int, offset *int) (*FollowingResponse, error) {
limitVal := 20
if limit != nil {
limitVal = *limit
}
offsetVal := 0
if offset != nil {
offsetVal = *offset
}
res, err := r.SubscribeClient.GetFollowing(ctx, &proto.GetFollowingRequest{
UserId: int32(obj.ID),
Limit: int32(limitVal),
Offset: int32(offsetVal),
})
if err != nil {
return nil, fmt.Errorf("failed to get following: %w", err)
}
return &FollowingResponse{
Following: convertProtoFollowingToGraphQL(res.Following),
TotalCount: int(res.TotalCount),
}, nil
}
// SubscriptionNotifications is the resolver for the subscriptionNotifications field.
func (r *userResolver) SubscriptionNotifications(ctx context.Context, obj *domain.User, unreadOnly *bool, limit *int, offset *int) (*NotificationsResponse, error) {
currentUserID, err := getUserIDFromContext(ctx)
if err != nil {
return nil, fmt.Errorf("authentication required: %w", err)
}
// Можно смотреть только свои уведомления
if obj.ID != currentUserID {
return nil, fmt.Errorf("access denied: can only view your own notifications")
}
unreadOnlyVal := false
if unreadOnly != nil {
unreadOnlyVal = *unreadOnly
}
limitVal := 20
if limit != nil {
limitVal = *limit
}
offsetVal := 0
if offset != nil {
offsetVal = *offset
}
res, err := r.SubscribeClient.GetSubscriptionNotifications(ctx, &proto.GetNotificationsRequest{
UserId: int32(obj.ID),
UnreadOnly: unreadOnlyVal,
Limit: int32(limitVal),
Offset: int32(offsetVal),
})
if err != nil {
return nil, fmt.Errorf("failed to get notifications: %w", err)
}
return &NotificationsResponse{
Notifications: convertProtoNotificationsToGraphQL(res.Notifications),
TotalCount: int(res.TotalCount),
UnreadCount: int(res.UnreadCount),
}, nil
}

View File

@ -10,128 +10,29 @@ import (
)
var (
// HTTP метрики
httpRequestsTotal = promauto.NewCounterVec(
prometheus.CounterOpts{
Name: "http_requests_total",
Help: "Total number of HTTP requests",
},
[]string{"method", "path", "status", "handler"},
[]string{"method", "path", "status"},
)
httpRequestDuration = promauto.NewHistogramVec(
prometheus.HistogramOpts{
Name: "http_request_duration_seconds",
Help: "Duration of HTTP requests",
Buckets: []float64{0.01, 0.05, 0.1, 0.5, 1, 2.5, 5, 10},
Buckets: []float64{0.1, 0.5, 1, 2.5, 5, 10},
},
[]string{"method", "path", "handler"},
[]string{"method", "path"},
)
// GraphQL специфичные метрики
gqlOperationsTotal = promauto.NewCounterVec(
prometheus.CounterOpts{
Name: "graphql_operations_total",
Help: "Total number of GraphQL operations",
httpResponseSize = promauto.NewSummaryVec(
prometheus.SummaryOpts{
Name: "http_response_size_bytes",
Help: "Size of HTTP responses",
},
[]string{"operation", "type", "name", "success"},
)
gqlOperationDuration = promauto.NewHistogramVec(
prometheus.HistogramOpts{
Name: "graphql_operation_duration_seconds",
Help: "Duration of GraphQL operations",
Buckets: []float64{0.001, 0.005, 0.01, 0.05, 0.1, 0.5, 1, 2, 5},
},
[]string{"operation", "name"},
)
// Бизнес метрики
usersTotal = promauto.NewGauge(
prometheus.GaugeOpts{
Name: "users_total",
Help: "Total number of registered users",
},
)
postsTotal = promauto.NewGauge(
prometheus.GaugeOpts{
Name: "posts_total",
Help: "Total number of posts",
},
)
commentsTotal = promauto.NewGauge(
prometheus.GaugeOpts{
Name: "comments_total",
Help: "Total number of comments",
},
)
messagesTotal = promauto.NewGauge(
prometheus.GaugeOpts{
Name: "messages_total",
Help: "Total number of messages",
},
)
activeWebsockets = promauto.NewGauge(
prometheus.GaugeOpts{
Name: "websocket_connections_active",
Help: "Number of active WebSocket connections",
},
)
websocketMessagesTotal = promauto.NewCounterVec(
prometheus.CounterOpts{
Name: "websocket_messages_total",
Help: "Total number of WebSocket messages",
},
[]string{"direction", "type"},
)
// Метрики ошибок
errorsTotal = promauto.NewCounterVec(
prometheus.CounterOpts{
Name: "errors_total",
Help: "Total number of errors by type",
},
[]string{"type", "source", "severity"},
)
// Метрики базы данных
dbQueriesTotal = promauto.NewCounterVec(
prometheus.CounterOpts{
Name: "db_queries_total",
Help: "Total number of database queries",
},
[]string{"operation", "table", "success"},
)
dbQueryDuration = promauto.NewHistogramVec(
prometheus.HistogramOpts{
Name: "db_query_duration_seconds",
Help: "Duration of database queries",
Buckets: []float64{0.001, 0.005, 0.01, 0.05, 0.1, 0.5, 1, 2},
},
[]string{"operation", "table"},
)
// Метрики кэша
cacheHitsTotal = promauto.NewCounterVec(
prometheus.CounterOpts{
Name: "cache_hits_total",
Help: "Total number of cache hits",
},
[]string{"type", "name"},
)
cacheMissesTotal = promauto.NewCounterVec(
prometheus.CounterOpts{
Name: "cache_misses_total",
Help: "Total number of cache misses",
},
[]string{"type", "name"},
[]string{"method", "path"},
)
)
@ -146,108 +47,21 @@ func MetricsMiddleware(next http.Handler) http.Handler {
duration := time.Since(start).Seconds()
status := strconv.Itoa(rw.status)
// Определяем handler type
handlerType := "http"
if r.URL.Path == "/query" {
handlerType = "graphql"
} else if r.URL.Path == "/ws" {
handlerType = "websocket"
}
// Регистрируем метрики
httpRequestsTotal.WithLabelValues(
r.Method,
r.URL.Path,
status,
handlerType,
).Inc()
httpRequestDuration.WithLabelValues(
r.Method,
r.URL.Path,
handlerType,
).Observe(duration)
httpResponseSize.WithLabelValues(
r.Method,
r.URL.Path,
).Observe(float64(rw.size))
})
}
// GraphQLMetricsMiddleware для отслеживания GraphQL операций
func GraphQLMetricsMiddleware() func(http.Handler) http.Handler {
return func(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if r.URL.Path != "/query" {
next.ServeHTTP(w, r)
return
}
// Здесь можно парсить GraphQL запрос и извлекать информацию об операции
// Для простоты пока пропускаем
next.ServeHTTP(w, r)
})
}
}
// Вспомогательные функции для обновления метрик
func IncGQLOperation(operationType, operationName string, success bool, duration time.Duration) {
status := "false"
if success {
status = "true"
}
gqlOperationsTotal.WithLabelValues(
operationType, // operation
operationName, // type (возможно нужно переименовать)
operationName, // name (дублирование)
status, // success
).Inc()
gqlOperationDuration.WithLabelValues(
operationType,
operationName,
).Observe(duration.Seconds())
}
func IncWebSocketMessage(direction, messageType string) {
websocketMessagesTotal.WithLabelValues(direction, messageType).Inc()
}
func SetActiveWebsockets(count int) {
activeWebsockets.Set(float64(count))
}
func IncError(errorType, source, severity string) {
errorsTotal.WithLabelValues(errorType, source, severity).Inc()
}
func IncDBQuery(operation, table string, success bool, duration time.Duration) {
status := "false"
if success {
status = "true"
}
dbQueriesTotal.WithLabelValues(operation, table, status).Inc()
dbQueryDuration.WithLabelValues(operation, table).Observe(duration.Seconds())
}
func IncCacheHit(cacheType, cacheName string) {
cacheHitsTotal.WithLabelValues(cacheType, cacheName).Inc()
}
func IncCacheMiss(cacheType, cacheName string) {
cacheMissesTotal.WithLabelValues(cacheType, cacheName).Inc()
}
func SetUsersCount(count int) {
usersTotal.Set(float64(count))
}
func SetPostsCount(count int) {
postsTotal.Set(float64(count))
}
func SetCommentsCount(count int) {
commentsTotal.Set(float64(count))
}
func SetMessagesCount(count int) {
messagesTotal.Set(float64(count))
}

View File

@ -66,7 +66,7 @@ func (s *Server) configureRouter() {
s.router.Use(middleware.CORS(allowedOrigins))
// Основной GraphQL обработчик
resolver := graph.NewResolver(s.services, s.db, s.services.Messages)
resolver := graph.NewResolver(s.services, s.db, s.services.Messages, s.services.Subscribe)
srv := handler.NewDefaultServer(graph.NewExecutableSchema(graph.Config{
Resolvers: resolver,
}))

View File

@ -6,29 +6,31 @@ import (
)
type Services struct {
Auth AuthService
User UserService
Post PostService
Comment CommentService
Like LikeService
Session SessionService
Mail MailService
Recovery RecoveryService
Audit AuditService
Messages proto.MessageServiceClient
Auth AuthService
User UserService
Post PostService
Comment CommentService
Like LikeService
Session SessionService
Mail MailService
Recovery RecoveryService
Audit AuditService
Messages proto.MessageServiceClient
Subscribe proto.SubscribeServiceClient
}
func NewServices(authService AuthService, userService UserService, postService PostService, commentService CommentService, likeService LikeService, mailService MailService, auditService AuditService, recoveryService RecoveryService, sessionService SessionService, messages proto.MessageServiceClient) *Services {
func NewServices(authService AuthService, userService UserService, postService PostService, commentService CommentService, likeService LikeService, mailService MailService, auditService AuditService, recoveryService RecoveryService, sessionService SessionService, messages proto.MessageServiceClient, subscribe proto.SubscribeServiceClient) *Services {
return &Services{
Auth: authService,
User: userService,
Post: postService,
Comment: commentService,
Like: likeService,
Session: sessionService,
Mail: mailService,
Recovery: recoveryService,
Audit: auditService,
Messages: messages,
Auth: authService,
User: userService,
Post: postService,
Comment: commentService,
Like: likeService,
Session: sessionService,
Mail: mailService,
Recovery: recoveryService,
Audit: auditService,
Messages: messages,
Subscribe: subscribe,
}
}

1304
proto/subscribe.pb.go Normal file

File diff suppressed because it is too large Load Diff

154
proto/subscribe.proto Normal file
View File

@ -0,0 +1,154 @@
syntax = "proto3";
package proto;
option go_package = ".;proto";
// Сервис для работы с подписками
service SubscribeService {
// Подписаться на пользователя
rpc FollowUser (FollowRequest) returns (FollowResponse);
// Отписаться от пользователя
rpc UnfollowUser (UnfollowRequest) returns (UnfollowResponse);
// Получить список подписчиков пользователя
rpc GetFollowers (GetFollowersRequest) returns (GetFollowersResponse);
// Получить список подписок пользователя
rpc GetFollowing (GetFollowingRequest) returns (GetFollowingResponse);
// Проверить, подписан ли пользователь на другого
rpc IsFollowing (IsFollowingRequest) returns (IsFollowingResponse);
// Получить уведомления о подписках для пользователя
rpc GetSubscriptionNotifications (GetNotificationsRequest) returns (GetNotificationsResponse);
// Пометить уведомление как прочитанное
rpc MarkNotificationAsRead (MarkNotificationReadRequest) returns (MarkNotificationReadResponse);
// Получить только КОЛИЧЕСТВО подписчиков
rpc GetFollowersCount (GetCountRequest) returns (GetCountResponse);
// Получить только КОЛИЧЕСТВО подписок
rpc GetFollowingCount (GetCountRequest) returns (GetCountResponse);
}
message GetCountRequest {
int32 user_id = 1;
}
// Ответ с количеством
message GetCountResponse {
int32 count = 1;
}
// Запрос на подписку
message FollowRequest {
int32 follower_id = 1; // ID пользователя, который подписывается
int32 following_id = 2; // ID пользователя, на которого подписываются
}
// Ответ на подписку
message FollowResponse {
bool success = 1; // Успешность операции
string message = 2; // Сообщение (например, об ошибке)
int32 subscription_id = 3; // ID созданной подписки
int32 notification_id = 4; // ID созданного уведомления
}
// Запрос на отписку
message UnfollowRequest {
int32 follower_id = 1;
int32 following_id = 2;
}
// Ответ на отписку
message UnfollowResponse {
bool success = 1;
string message = 2;
}
// Запрос на получение подписчиков
message GetFollowersRequest {
int32 user_id = 1; // ID пользователя, чьих подписчиков запрашиваем
int32 limit = 2; // Лимит (для пагинации)
int32 offset = 3; // Смещение (для пагинации)
}
// Ответ с подписчиками
message GetFollowersResponse {
repeated Follower followers = 1; // Список подписчиков
int32 total_count = 2; // Общее количество подписчиков
message Follower {
int32 user_id = 1;
string username = 2;
string avatar = 3;
}
}
// Запрос на получение подписок
message GetFollowingRequest {
int32 user_id = 1; // ID пользователя, чьи подписки запрашиваем
int32 limit = 2;
int32 offset = 3;
}
// Ответ с подписками
message GetFollowingResponse {
repeated Following following = 1; // Список подписок
int32 total_count = 2; // Общее количество подписок
message Following {
int32 user_id = 1;
string username = 2;
string avatar = 3;
}
}
// Запрос на проверку подписки
message IsFollowingRequest {
int32 follower_id = 1;
int32 following_id = 2;
}
// Ответ на проверку подписки
message IsFollowingResponse {
bool is_following = 1; // true - подписан, false - не подписан
}
// Запрос на получение уведомлений
message GetNotificationsRequest {
int32 user_id = 1; // ID пользователя, чьи уведомления запрашиваем
bool unread_only = 2; // Только непрочитанные
int32 limit = 3;
int32 offset = 4;
}
// Ответ с уведомлениями
message GetNotificationsResponse {
repeated Notification notifications = 1;
int32 total_count = 2;
int32 unread_count = 3; // Количество непрочитанных уведомлений
message Notification {
int32 id = 1;
int32 subscription_id = 2;
int32 follower_id = 3;
string follower_username = 4;
string follower_avatar = 5;
bool is_read = 6;
string created_at = 7;
string expires_at = 8;
}
}
// Запрос на пометку уведомления как прочитанного
message MarkNotificationReadRequest {
int32 notification_id = 1;
int32 user_id = 2; // Для проверки прав
}
// Ответ на пометку уведомления
message MarkNotificationReadResponse {
bool success = 1;
string message = 2;
}

447
proto/subscribe_grpc.pb.go Normal file
View File

@ -0,0 +1,447 @@
// Code generated by protoc-gen-go-grpc. DO NOT EDIT.
// versions:
// - protoc-gen-go-grpc v1.5.1
// - protoc v3.21.12
// source: subscribe.proto
package proto
import (
context "context"
grpc "google.golang.org/grpc"
codes "google.golang.org/grpc/codes"
status "google.golang.org/grpc/status"
)
// This is a compile-time assertion to ensure that this generated file
// is compatible with the grpc package it is being compiled against.
// Requires gRPC-Go v1.64.0 or later.
const _ = grpc.SupportPackageIsVersion9
const (
SubscribeService_FollowUser_FullMethodName = "/proto.SubscribeService/FollowUser"
SubscribeService_UnfollowUser_FullMethodName = "/proto.SubscribeService/UnfollowUser"
SubscribeService_GetFollowers_FullMethodName = "/proto.SubscribeService/GetFollowers"
SubscribeService_GetFollowing_FullMethodName = "/proto.SubscribeService/GetFollowing"
SubscribeService_IsFollowing_FullMethodName = "/proto.SubscribeService/IsFollowing"
SubscribeService_GetSubscriptionNotifications_FullMethodName = "/proto.SubscribeService/GetSubscriptionNotifications"
SubscribeService_MarkNotificationAsRead_FullMethodName = "/proto.SubscribeService/MarkNotificationAsRead"
SubscribeService_GetFollowersCount_FullMethodName = "/proto.SubscribeService/GetFollowersCount"
SubscribeService_GetFollowingCount_FullMethodName = "/proto.SubscribeService/GetFollowingCount"
)
// SubscribeServiceClient is the client API for SubscribeService service.
//
// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream.
//
// Сервис для работы с подписками
type SubscribeServiceClient interface {
// Подписаться на пользователя
FollowUser(ctx context.Context, in *FollowRequest, opts ...grpc.CallOption) (*FollowResponse, error)
// Отписаться от пользователя
UnfollowUser(ctx context.Context, in *UnfollowRequest, opts ...grpc.CallOption) (*UnfollowResponse, error)
// Получить список подписчиков пользователя
GetFollowers(ctx context.Context, in *GetFollowersRequest, opts ...grpc.CallOption) (*GetFollowersResponse, error)
// Получить список подписок пользователя
GetFollowing(ctx context.Context, in *GetFollowingRequest, opts ...grpc.CallOption) (*GetFollowingResponse, error)
// Проверить, подписан ли пользователь на другого
IsFollowing(ctx context.Context, in *IsFollowingRequest, opts ...grpc.CallOption) (*IsFollowingResponse, error)
// Получить уведомления о подписках для пользователя
GetSubscriptionNotifications(ctx context.Context, in *GetNotificationsRequest, opts ...grpc.CallOption) (*GetNotificationsResponse, error)
// Пометить уведомление как прочитанное
MarkNotificationAsRead(ctx context.Context, in *MarkNotificationReadRequest, opts ...grpc.CallOption) (*MarkNotificationReadResponse, error)
// Получить только КОЛИЧЕСТВО подписчиков
GetFollowersCount(ctx context.Context, in *GetCountRequest, opts ...grpc.CallOption) (*GetCountResponse, error)
// Получить только КОЛИЧЕСТВО подписок
GetFollowingCount(ctx context.Context, in *GetCountRequest, opts ...grpc.CallOption) (*GetCountResponse, error)
}
type subscribeServiceClient struct {
cc grpc.ClientConnInterface
}
func NewSubscribeServiceClient(cc grpc.ClientConnInterface) SubscribeServiceClient {
return &subscribeServiceClient{cc}
}
func (c *subscribeServiceClient) FollowUser(ctx context.Context, in *FollowRequest, opts ...grpc.CallOption) (*FollowResponse, error) {
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
out := new(FollowResponse)
err := c.cc.Invoke(ctx, SubscribeService_FollowUser_FullMethodName, in, out, cOpts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *subscribeServiceClient) UnfollowUser(ctx context.Context, in *UnfollowRequest, opts ...grpc.CallOption) (*UnfollowResponse, error) {
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
out := new(UnfollowResponse)
err := c.cc.Invoke(ctx, SubscribeService_UnfollowUser_FullMethodName, in, out, cOpts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *subscribeServiceClient) GetFollowers(ctx context.Context, in *GetFollowersRequest, opts ...grpc.CallOption) (*GetFollowersResponse, error) {
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
out := new(GetFollowersResponse)
err := c.cc.Invoke(ctx, SubscribeService_GetFollowers_FullMethodName, in, out, cOpts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *subscribeServiceClient) GetFollowing(ctx context.Context, in *GetFollowingRequest, opts ...grpc.CallOption) (*GetFollowingResponse, error) {
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
out := new(GetFollowingResponse)
err := c.cc.Invoke(ctx, SubscribeService_GetFollowing_FullMethodName, in, out, cOpts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *subscribeServiceClient) IsFollowing(ctx context.Context, in *IsFollowingRequest, opts ...grpc.CallOption) (*IsFollowingResponse, error) {
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
out := new(IsFollowingResponse)
err := c.cc.Invoke(ctx, SubscribeService_IsFollowing_FullMethodName, in, out, cOpts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *subscribeServiceClient) GetSubscriptionNotifications(ctx context.Context, in *GetNotificationsRequest, opts ...grpc.CallOption) (*GetNotificationsResponse, error) {
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
out := new(GetNotificationsResponse)
err := c.cc.Invoke(ctx, SubscribeService_GetSubscriptionNotifications_FullMethodName, in, out, cOpts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *subscribeServiceClient) MarkNotificationAsRead(ctx context.Context, in *MarkNotificationReadRequest, opts ...grpc.CallOption) (*MarkNotificationReadResponse, error) {
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
out := new(MarkNotificationReadResponse)
err := c.cc.Invoke(ctx, SubscribeService_MarkNotificationAsRead_FullMethodName, in, out, cOpts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *subscribeServiceClient) GetFollowersCount(ctx context.Context, in *GetCountRequest, opts ...grpc.CallOption) (*GetCountResponse, error) {
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
out := new(GetCountResponse)
err := c.cc.Invoke(ctx, SubscribeService_GetFollowersCount_FullMethodName, in, out, cOpts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *subscribeServiceClient) GetFollowingCount(ctx context.Context, in *GetCountRequest, opts ...grpc.CallOption) (*GetCountResponse, error) {
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
out := new(GetCountResponse)
err := c.cc.Invoke(ctx, SubscribeService_GetFollowingCount_FullMethodName, in, out, cOpts...)
if err != nil {
return nil, err
}
return out, nil
}
// SubscribeServiceServer is the server API for SubscribeService service.
// All implementations must embed UnimplementedSubscribeServiceServer
// for forward compatibility.
//
// Сервис для работы с подписками
type SubscribeServiceServer interface {
// Подписаться на пользователя
FollowUser(context.Context, *FollowRequest) (*FollowResponse, error)
// Отписаться от пользователя
UnfollowUser(context.Context, *UnfollowRequest) (*UnfollowResponse, error)
// Получить список подписчиков пользователя
GetFollowers(context.Context, *GetFollowersRequest) (*GetFollowersResponse, error)
// Получить список подписок пользователя
GetFollowing(context.Context, *GetFollowingRequest) (*GetFollowingResponse, error)
// Проверить, подписан ли пользователь на другого
IsFollowing(context.Context, *IsFollowingRequest) (*IsFollowingResponse, error)
// Получить уведомления о подписках для пользователя
GetSubscriptionNotifications(context.Context, *GetNotificationsRequest) (*GetNotificationsResponse, error)
// Пометить уведомление как прочитанное
MarkNotificationAsRead(context.Context, *MarkNotificationReadRequest) (*MarkNotificationReadResponse, error)
// Получить только КОЛИЧЕСТВО подписчиков
GetFollowersCount(context.Context, *GetCountRequest) (*GetCountResponse, error)
// Получить только КОЛИЧЕСТВО подписок
GetFollowingCount(context.Context, *GetCountRequest) (*GetCountResponse, error)
mustEmbedUnimplementedSubscribeServiceServer()
}
// UnimplementedSubscribeServiceServer must be embedded to have
// forward compatible implementations.
//
// NOTE: this should be embedded by value instead of pointer to avoid a nil
// pointer dereference when methods are called.
type UnimplementedSubscribeServiceServer struct{}
func (UnimplementedSubscribeServiceServer) FollowUser(context.Context, *FollowRequest) (*FollowResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method FollowUser not implemented")
}
func (UnimplementedSubscribeServiceServer) UnfollowUser(context.Context, *UnfollowRequest) (*UnfollowResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method UnfollowUser not implemented")
}
func (UnimplementedSubscribeServiceServer) GetFollowers(context.Context, *GetFollowersRequest) (*GetFollowersResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method GetFollowers not implemented")
}
func (UnimplementedSubscribeServiceServer) GetFollowing(context.Context, *GetFollowingRequest) (*GetFollowingResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method GetFollowing not implemented")
}
func (UnimplementedSubscribeServiceServer) IsFollowing(context.Context, *IsFollowingRequest) (*IsFollowingResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method IsFollowing not implemented")
}
func (UnimplementedSubscribeServiceServer) GetSubscriptionNotifications(context.Context, *GetNotificationsRequest) (*GetNotificationsResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method GetSubscriptionNotifications not implemented")
}
func (UnimplementedSubscribeServiceServer) MarkNotificationAsRead(context.Context, *MarkNotificationReadRequest) (*MarkNotificationReadResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method MarkNotificationAsRead not implemented")
}
func (UnimplementedSubscribeServiceServer) GetFollowersCount(context.Context, *GetCountRequest) (*GetCountResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method GetFollowersCount not implemented")
}
func (UnimplementedSubscribeServiceServer) GetFollowingCount(context.Context, *GetCountRequest) (*GetCountResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method GetFollowingCount not implemented")
}
func (UnimplementedSubscribeServiceServer) mustEmbedUnimplementedSubscribeServiceServer() {}
func (UnimplementedSubscribeServiceServer) testEmbeddedByValue() {}
// UnsafeSubscribeServiceServer may be embedded to opt out of forward compatibility for this service.
// Use of this interface is not recommended, as added methods to SubscribeServiceServer will
// result in compilation errors.
type UnsafeSubscribeServiceServer interface {
mustEmbedUnimplementedSubscribeServiceServer()
}
func RegisterSubscribeServiceServer(s grpc.ServiceRegistrar, srv SubscribeServiceServer) {
// If the following call pancis, it indicates UnimplementedSubscribeServiceServer was
// embedded by pointer and is nil. This will cause panics if an
// unimplemented method is ever invoked, so we test this at initialization
// time to prevent it from happening at runtime later due to I/O.
if t, ok := srv.(interface{ testEmbeddedByValue() }); ok {
t.testEmbeddedByValue()
}
s.RegisterService(&SubscribeService_ServiceDesc, srv)
}
func _SubscribeService_FollowUser_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(FollowRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(SubscribeServiceServer).FollowUser(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: SubscribeService_FollowUser_FullMethodName,
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(SubscribeServiceServer).FollowUser(ctx, req.(*FollowRequest))
}
return interceptor(ctx, in, info, handler)
}
func _SubscribeService_UnfollowUser_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(UnfollowRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(SubscribeServiceServer).UnfollowUser(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: SubscribeService_UnfollowUser_FullMethodName,
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(SubscribeServiceServer).UnfollowUser(ctx, req.(*UnfollowRequest))
}
return interceptor(ctx, in, info, handler)
}
func _SubscribeService_GetFollowers_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(GetFollowersRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(SubscribeServiceServer).GetFollowers(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: SubscribeService_GetFollowers_FullMethodName,
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(SubscribeServiceServer).GetFollowers(ctx, req.(*GetFollowersRequest))
}
return interceptor(ctx, in, info, handler)
}
func _SubscribeService_GetFollowing_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(GetFollowingRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(SubscribeServiceServer).GetFollowing(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: SubscribeService_GetFollowing_FullMethodName,
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(SubscribeServiceServer).GetFollowing(ctx, req.(*GetFollowingRequest))
}
return interceptor(ctx, in, info, handler)
}
func _SubscribeService_IsFollowing_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(IsFollowingRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(SubscribeServiceServer).IsFollowing(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: SubscribeService_IsFollowing_FullMethodName,
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(SubscribeServiceServer).IsFollowing(ctx, req.(*IsFollowingRequest))
}
return interceptor(ctx, in, info, handler)
}
func _SubscribeService_GetSubscriptionNotifications_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(GetNotificationsRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(SubscribeServiceServer).GetSubscriptionNotifications(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: SubscribeService_GetSubscriptionNotifications_FullMethodName,
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(SubscribeServiceServer).GetSubscriptionNotifications(ctx, req.(*GetNotificationsRequest))
}
return interceptor(ctx, in, info, handler)
}
func _SubscribeService_MarkNotificationAsRead_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(MarkNotificationReadRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(SubscribeServiceServer).MarkNotificationAsRead(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: SubscribeService_MarkNotificationAsRead_FullMethodName,
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(SubscribeServiceServer).MarkNotificationAsRead(ctx, req.(*MarkNotificationReadRequest))
}
return interceptor(ctx, in, info, handler)
}
func _SubscribeService_GetFollowersCount_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(GetCountRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(SubscribeServiceServer).GetFollowersCount(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: SubscribeService_GetFollowersCount_FullMethodName,
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(SubscribeServiceServer).GetFollowersCount(ctx, req.(*GetCountRequest))
}
return interceptor(ctx, in, info, handler)
}
func _SubscribeService_GetFollowingCount_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(GetCountRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(SubscribeServiceServer).GetFollowingCount(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: SubscribeService_GetFollowingCount_FullMethodName,
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(SubscribeServiceServer).GetFollowingCount(ctx, req.(*GetCountRequest))
}
return interceptor(ctx, in, info, handler)
}
// SubscribeService_ServiceDesc is the grpc.ServiceDesc for SubscribeService service.
// It's only intended for direct use with grpc.RegisterService,
// and not to be introspected or modified (even as a copy)
var SubscribeService_ServiceDesc = grpc.ServiceDesc{
ServiceName: "proto.SubscribeService",
HandlerType: (*SubscribeServiceServer)(nil),
Methods: []grpc.MethodDesc{
{
MethodName: "FollowUser",
Handler: _SubscribeService_FollowUser_Handler,
},
{
MethodName: "UnfollowUser",
Handler: _SubscribeService_UnfollowUser_Handler,
},
{
MethodName: "GetFollowers",
Handler: _SubscribeService_GetFollowers_Handler,
},
{
MethodName: "GetFollowing",
Handler: _SubscribeService_GetFollowing_Handler,
},
{
MethodName: "IsFollowing",
Handler: _SubscribeService_IsFollowing_Handler,
},
{
MethodName: "GetSubscriptionNotifications",
Handler: _SubscribeService_GetSubscriptionNotifications_Handler,
},
{
MethodName: "MarkNotificationAsRead",
Handler: _SubscribeService_MarkNotificationAsRead_Handler,
},
{
MethodName: "GetFollowersCount",
Handler: _SubscribeService_GetFollowersCount_Handler,
},
{
MethodName: "GetFollowingCount",
Handler: _SubscribeService_GetFollowingCount_Handler,
},
},
Streams: []grpc.StreamDesc{},
Metadata: "subscribe.proto",
}