-
Notifications
You must be signed in to change notification settings - Fork 27
[build] migrate to GoReleaser #189
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Conversation
WalkthroughRemoved legacy Docker build/publish workflows and added Goreleaser-based CI; introduced comprehensive lint/config files; large API and implementation refactors across modules (module factory functions, push client abstraction, exported cache/pubsub types, messages/auth/signature changes, added sentinel errors, constructors, and model/migration updates); many handler and test adjustments. Changes
Sequence Diagram(s)sequenceDiagram
participant FX as FX App
participant Module as Module() factory
participant NewClient as newClient()
participant Client as client.Client
participant PushSvc as Push Service
Note right of Module `#DFF1DF`: Module() factories replace package vars
FX->>Module: call Module()
Module-->>FX: provide configs, services, clients
Note right of NewClient `#FFF3DE`: construct concrete client (fcm/upstream)
FX->>NewClient: provide push.Config
NewClient-->>Client: return concrete client or error
Note right of PushSvc `#F3E8FF`: runtime lifecycle
FX->>PushSvc: OnStart -> open client
PushSvc->>Client: Open(ctx)
PushSvc->>Client: Send(ctx, []client.Message)
Client-->>PushSvc: results/errors
FX->>Client: Close(ctx) on stop
Estimated code review effort🎯 5 (Critical) | ⏱️ ~120+ minutes Areas warranting focused review:
Possibly related PRs
Suggested labels
Pre-merge checks and finishing touches❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✨ Finishing touches
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 4
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (2)
internal/sms-gateway/modules/push/upstream/client.go (1)
68-91: Restore upstream options when building the request.
c.optionsis now completely unused, so any upstream configuration (API token, base URL override, etc.) injected through the options map never makes it into the outgoing request. Deployments that relied on those values to authenticate requests (e.g., Authorization header or custom endpoint) will start getting 401/404 responses even though their config hasn’t changed. Please reinstate the options handling (e.g., set headers/query params or derive the URL from the configured values) so existing upstream integrations keep working.internal/sms-gateway/modules/settings/repository.go (1)
33-48: Restore theWHEREplaceholder to avoid broken queries.
Where("user_id", settings.UserID)drops the= ?placeholder, so GORM buildsWHERE user_id, which either errors (PostgreSQL) or matches every non-empty row (MySQL). That breaks updates and defeats the per-user lock.Apply this fix:
- if err := tx.Clauses(clause.Locking{Strength: clause.LockingStrengthUpdate}).Where("user_id", settings.UserID).Limit(1).Find(source).Error; err != nil { + if err := tx.Clauses(clause.Locking{Strength: clause.LockingStrengthUpdate}).Where("user_id = ?", settings.UserID).Limit(1).Find(source).Error; err != nil {
🧹 Nitpick comments (6)
internal/config/config.go (1)
28-28: Cosmetic whitespace changes for alignment.These spacing adjustments in the struct tags have no functional impact. If vertical alignment is desired, consider applying a consistent formatting style across all struct fields in this file (or using an automated formatter like
gofmtorgofumpt).Also applies to: 77-77
internal/sms-gateway/modules/messages/cache.go (1)
63-63: Consider clarifying the nolint comment.The comment could be more explicit about what the "special meaning" is (e.g., "cache miss is not an error") to help future maintainers understand the intent.
- return nil, nil //nolint:nilnil //special meaning + return nil, nil //nolint:nilnil // cache miss is not an errorinternal/sms-gateway/handlers/middlewares/userauth/userauth.go (1)
94-100: Consider storing pointers in Locals to reduce allocations.The current pattern stores
models.Uservalues in Locals (lines 53, 80) then converts to*models.Userby taking the address of a local copy. This causes the value to escape to the heap.For better performance, consider storing
*models.Userdirectly in Locals throughout the flow, eliminating unnecessary allocations.Example refactor for NewBasic (line 53):
- c.Locals(localsUser, user) + c.Locals(localsUser, &user)And simplify GetUser:
func GetUser(c *fiber.Ctx) *models.User { - if user, ok := c.Locals(localsUser).(models.User); ok { - return &user + if user, ok := c.Locals(localsUser).(*models.User); ok { + return user } - return nil }internal/sms-gateway/pubsub/pubsub.go (1)
29-47: LGTM!The refactoring to an end-return pattern improves consistency with:
- Single return point
- Uniform error wrapping with "can't create pubsub: %w"
- Cleaner control flow
For slightly better clarity on line 32, you could use:
- pubSub, err = pubsub.NewMemory(opts...), nil + pubSub = pubsub.NewMemory(opts...) + err = nilThough the current form is valid and commonly used in Go.
.github/workflows/pr.yml (1)
75-76: Consider documenting the RELEASE_ID epoch date.The RELEASE_ID calculation uses
2022-06-15as an epoch date. Consider adding a comment explaining the significance of this date or linking to documentation, as this calculation will be used for versioning.Apply this diff:
+ # RELEASE_ID: Days since project inception (2022-06-15) - name: Set RELEASE_ID env run: echo RELEASE_ID=$(( ($(date +%s) - $(date -d "2022-06-15" +%s)) / 86400 )) >> ${GITHUB_ENV}internal/sms-gateway/handlers/messages/mobile.go (1)
35-43: Consider aligning constructor patterns across mobile controllers.This constructor uses fx.In-based dependency injection, while
internal/sms-gateway/handlers/settings/mobile.go(line 23-37) uses explicit parameters. The PR summary mentions "converting module variables to factory functions," suggesting a move toward explicit dependencies. Consider standardizing the constructor pattern across all mobile controllers for consistency.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
⛔ Files ignored due to path filters (1)
go.sumis excluded by!**/*.sum
📒 Files selected for processing (74)
.github/workflows/docker-build.yml(0 hunks).github/workflows/docker-publish.yml(0 hunks).github/workflows/go.yml(3 hunks).github/workflows/pr.yml(1 hunks).github/workflows/release.yml(2 hunks).gitignore(1 hunks).golangci.yml(1 hunks).goreleaser.yaml(1 hunks)Dockerfile.goreleaser(1 hunks)Makefile(2 hunks)internal/config/config.go(2 hunks)internal/sms-gateway/app.go(2 hunks)internal/sms-gateway/cache/errors.go(1 hunks)internal/sms-gateway/cache/factory.go(2 hunks)internal/sms-gateway/handlers/base/handler_test.go(5 hunks)internal/sms-gateway/handlers/converters/messages.go(1 hunks)internal/sms-gateway/handlers/devices/3rdparty.go(3 hunks)internal/sms-gateway/handlers/events/mobile.go(1 hunks)internal/sms-gateway/handlers/health.go(2 hunks)internal/sms-gateway/handlers/logs/3rdparty.go(2 hunks)internal/sms-gateway/handlers/messages/3rdparty.go(8 hunks)internal/sms-gateway/handlers/messages/mobile.go(2 hunks)internal/sms-gateway/handlers/messages/params.go(4 hunks)internal/sms-gateway/handlers/middlewares/userauth/userauth.go(2 hunks)internal/sms-gateway/handlers/mobile.go(7 hunks)internal/sms-gateway/handlers/root.go(1 hunks)internal/sms-gateway/handlers/settings/3rdparty.go(4 hunks)internal/sms-gateway/handlers/settings/mobile.go(2 hunks)internal/sms-gateway/handlers/upstream.go(2 hunks)internal/sms-gateway/handlers/webhooks/3rdparty.go(4 hunks)internal/sms-gateway/handlers/webhooks/mobile.go(2 hunks)internal/sms-gateway/models/models.go(2 hunks)internal/sms-gateway/modules/auth/errors.go(1 hunks)internal/sms-gateway/modules/auth/module.go(1 hunks)internal/sms-gateway/modules/auth/repository.go(1 hunks)internal/sms-gateway/modules/auth/service.go(6 hunks)internal/sms-gateway/modules/auth/types.go(1 hunks)internal/sms-gateway/modules/devices/service.go(2 hunks)internal/sms-gateway/modules/events/errors.go(1 hunks)internal/sms-gateway/modules/events/metrics.go(3 hunks)internal/sms-gateway/modules/events/module.go(1 hunks)internal/sms-gateway/modules/events/service.go(6 hunks)internal/sms-gateway/modules/events/types.go(2 hunks)internal/sms-gateway/modules/health/service.go(1 hunks)internal/sms-gateway/modules/messages/cache.go(2 hunks)internal/sms-gateway/modules/messages/converters.go(1 hunks)internal/sms-gateway/modules/messages/errors.go(1 hunks)internal/sms-gateway/modules/messages/models.go(6 hunks)internal/sms-gateway/modules/messages/module.go(1 hunks)internal/sms-gateway/modules/messages/repository.go(5 hunks)internal/sms-gateway/modules/messages/repository_filter.go(2 hunks)internal/sms-gateway/modules/messages/service.go(10 hunks)internal/sms-gateway/modules/push/client.go(1 hunks)internal/sms-gateway/modules/push/client/types.go(1 hunks)internal/sms-gateway/modules/push/fcm/client.go(4 hunks)internal/sms-gateway/modules/push/fcm/errors.go(1 hunks)internal/sms-gateway/modules/push/fcm/utils.go(1 hunks)internal/sms-gateway/modules/push/module.go(2 hunks)internal/sms-gateway/modules/push/service.go(11 hunks)internal/sms-gateway/modules/push/types.go(3 hunks)internal/sms-gateway/modules/push/upstream/client.go(5 hunks)internal/sms-gateway/modules/settings/models.go(1 hunks)internal/sms-gateway/modules/settings/module.go(1 hunks)internal/sms-gateway/modules/settings/repository.go(5 hunks)internal/sms-gateway/modules/sse/service.go(3 hunks)internal/sms-gateway/modules/webhooks/models.go(1 hunks)internal/sms-gateway/openapi/docs.go(5 hunks)internal/sms-gateway/pubsub/module.go(2 hunks)internal/sms-gateway/pubsub/pubsub.go(1 hunks)pkg/cache/memory_edge_test.go(1 hunks)pkg/cache/redis.go(2 hunks)pkg/pubsub/redis.go(2 hunks)test/e2e/mobile_test.go(1 hunks)test/e2e/utils_test.go(1 hunks)
💤 Files with no reviewable changes (2)
- .github/workflows/docker-publish.yml
- .github/workflows/docker-build.yml
🧰 Additional context used
🧠 Learnings (5)
📚 Learning: 2025-09-10T23:45:45.502Z
Learnt from: capcom6
Repo: android-sms-gateway/server PR: 178
File: internal/sms-gateway/online/module.go:14-16
Timestamp: 2025-09-10T23:45:45.502Z
Learning: fx.Decorate in uber-go/fx creates module-scoped decorated dependencies, not global modifications. Using fx.Decorate(func(log *zap.Logger) *zap.Logger { return log.Named("module_name") }) is a correct pattern for creating module-specific loggers that are scoped only to that module's dependency graph.
Applied to files:
internal/sms-gateway/modules/auth/module.gointernal/sms-gateway/modules/settings/module.gointernal/sms-gateway/pubsub/module.gointernal/sms-gateway/modules/events/module.go
📚 Learning: 2025-07-22T00:31:28.796Z
Learnt from: capcom6
Repo: android-sms-gateway/server PR: 154
File: internal/sms-gateway/modules/sse/service.go:67-79
Timestamp: 2025-07-22T00:31:28.796Z
Learning: In the SSE service Send method (internal/sms-gateway/modules/sse/service.go), blocking on full channels is intentional and acceptable. Timeout control is handled at the calling code level through context, not within the Send method itself. The user prefers this approach over adding a default case that would drop messages when channels are full.
Applied to files:
internal/sms-gateway/modules/events/service.gointernal/sms-gateway/modules/sse/service.go
📚 Learning: 2025-09-05T08:37:28.077Z
Learnt from: eznix86
Repo: android-sms-gateway/server PR: 172
File: deployments/kubernetes/sms-gateway-server/Chart.yaml:9-10
Timestamp: 2025-09-05T08:37:28.077Z
Learning: In the android-sms-gateway/server project, the Helm chart's appVersion in Chart.yaml is set to "latest" as a placeholder because CI automatically updates it with the actual version from git tags during the release process via the bump-chart-app-version.yaml workflow.
Applied to files:
.goreleaser.yaml
📚 Learning: 2025-09-10T23:53:42.006Z
Learnt from: capcom6
Repo: android-sms-gateway/server PR: 178
File: pkg/cache/redis.go:63-75
Timestamp: 2025-09-10T23:53:42.006Z
Learning: In pkg/cache/redis.go, the Redis cache implementation uses Redis's native field-level TTL via HExpireAt, meaning expired fields are automatically removed by Redis itself. This differs from the memory implementation where the application tracks expiration times. As a result, operations like Drain that call HGetAll will only return non-expired items since expired fields are already removed by Redis.
Applied to files:
pkg/cache/redis.go
📚 Learning: 2025-07-22T00:34:31.896Z
Learnt from: capcom6
Repo: android-sms-gateway/server PR: 154
File: internal/sms-gateway/modules/sse/service.go:89-92
Timestamp: 2025-07-22T00:34:31.896Z
Learning: In Go SSE implementations, when cleaning up connections with buffered channels, avoid closing the data channels directly as this can cause panics if concurrent goroutines attempt to write to them. Instead, use a separate close signal channel to notify writers that the connection should be terminated, allowing the data channels to be garbage collected naturally when the connection is removed.
Applied to files:
internal/sms-gateway/modules/sse/service.go
🧬 Code graph analysis (42)
internal/sms-gateway/cache/errors.go (1)
internal/sms-gateway/modules/push/service.go (1)
New(43-73)
internal/sms-gateway/modules/health/service.go (1)
internal/sms-gateway/modules/health/types.go (3)
HealthProvider(66-72)Checks(64-64)CheckResult(27-30)
internal/sms-gateway/modules/auth/module.go (2)
internal/sms-gateway/app.go (2)
Module(36-62)Run(64-75)internal/sms-gateway/modules/events/module.go (1)
Module(10-39)
internal/sms-gateway/handlers/middlewares/userauth/userauth.go (1)
internal/sms-gateway/models/models.go (1)
User(17-23)
internal/sms-gateway/handlers/events/mobile.go (2)
internal/sms-gateway/handlers/messages/mobile.go (1)
MobileController(29-33)internal/sms-gateway/models/models.go (1)
Device(32-43)
internal/sms-gateway/modules/push/client.go (5)
internal/sms-gateway/modules/push/service.go (2)
Config(23-30)New(43-73)internal/sms-gateway/modules/push/client/types.go (1)
Client(9-13)internal/sms-gateway/modules/push/fcm/client.go (2)
Client(14-19)New(21-27)internal/sms-gateway/modules/push/upstream/client.go (2)
Client(22-27)New(29-35)internal/sms-gateway/modules/push/types.go (3)
Mode(10-10)ModeFCM(13-13)ModeUpstream(14-14)
internal/sms-gateway/modules/auth/repository.go (1)
internal/sms-gateway/models/models.go (1)
User(17-23)
internal/sms-gateway/modules/messages/models.go (1)
internal/sms-gateway/models/models.go (1)
SoftDeletableModel(12-15)
internal/sms-gateway/modules/devices/service.go (3)
internal/sms-gateway/models/models.go (1)
Device(32-43)internal/sms-gateway/modules/devices/repository_filter.go (4)
SelectFilter(9-9)WithUserID(23-27)WithID(11-15)ActiveWithin(29-33)internal/sms-gateway/modules/devices/repository.go (1)
ErrNotFound(13-13)
internal/sms-gateway/modules/settings/models.go (3)
internal/sms-gateway/models/models.go (1)
TimedModel(7-10)internal/sms-gateway/modules/messages/models.go (1)
Migrate(136-141)internal/sms-gateway/modules/webhooks/models.go (1)
Migrate(25-27)
internal/sms-gateway/modules/events/service.go (7)
internal/sms-gateway/pubsub/pubsub.go (1)
PubSub(14-14)internal/sms-gateway/modules/events/errors.go (1)
ErrValidationFailed(6-6)internal/sms-gateway/modules/events/metrics.go (6)
DeliveryTypeUnknown(20-20)FailureReasonPublishError(23-23)EventTypeUnknown(26-26)FailureReasonSerializationError(22-22)DeliveryTypePush(18-18)FailureReasonProviderFailed(24-24)internal/sms-gateway/modules/events/types.go (1)
Event(10-13)internal/sms-gateway/modules/push/client/types.go (1)
Event(20-23)internal/sms-gateway/modules/push/types.go (1)
Event(17-17)internal/sms-gateway/modules/sse/types.go (1)
Event(7-10)
internal/sms-gateway/modules/settings/repository.go (1)
internal/sms-gateway/modules/settings/models.go (1)
DeviceSettings(10-17)
internal/sms-gateway/modules/push/module.go (4)
internal/sms-gateway/modules/push/fcm/client.go (2)
New(21-27)Client(14-19)internal/sms-gateway/modules/push/service.go (1)
New(43-73)internal/sms-gateway/modules/push/upstream/client.go (2)
New(29-35)Client(22-27)internal/sms-gateway/modules/push/client/types.go (1)
Client(9-13)
internal/sms-gateway/modules/settings/module.go (3)
internal/sms-gateway/app.go (1)
Module(36-62)internal/sms-gateway/modules/messages/module.go (1)
Module(11-35)internal/sms-gateway/handlers/module.go (1)
Module(15-39)
internal/sms-gateway/handlers/upstream.go (1)
internal/sms-gateway/handlers/config.go (1)
Config(3-11)
internal/sms-gateway/modules/messages/cache.go (2)
internal/sms-gateway/modules/messages/domain.go (1)
MessageStateOut(38-44)pkg/cache/options.go (2)
WithTTL(22-30)AndSetTTL(64-68)
internal/sms-gateway/handlers/settings/mobile.go (3)
internal/sms-gateway/handlers/events/mobile.go (2)
NewMobileController(19-27)MobileController(13-17)internal/sms-gateway/handlers/messages/mobile.go (2)
NewMobileController(35-43)MobileController(29-33)internal/sms-gateway/handlers/webhooks/mobile.go (2)
NewMobileController(21-33)MobileController(15-19)
internal/sms-gateway/modules/push/fcm/errors.go (2)
internal/sms-gateway/modules/push/fcm/client.go (1)
New(21-27)internal/sms-gateway/modules/push/upstream/client.go (1)
New(29-35)
internal/sms-gateway/modules/push/fcm/utils.go (2)
internal/sms-gateway/modules/push/client/types.go (1)
Event(20-23)internal/sms-gateway/modules/push/types.go (1)
Event(17-17)
internal/sms-gateway/pubsub/pubsub.go (2)
pkg/pubsub/memory.go (1)
NewMemory(24-36)pkg/pubsub/redis.go (2)
NewRedis(42-76)RedisConfig(15-27)
internal/sms-gateway/handlers/devices/3rdparty.go (5)
internal/sms-gateway/modules/devices/service.go (1)
Service(31-40)internal/sms-gateway/handlers/logs/3rdparty.go (2)
NewThirdPartyController(24-31)ThirdPartyController(20-22)internal/sms-gateway/handlers/messages/3rdparty.go (2)
NewThirdPartyController(44-53)ThirdPartyController(37-42)internal/sms-gateway/handlers/settings/3rdparty.go (2)
NewThirdPartyController(35-44)ThirdPartyController(28-33)internal/sms-gateway/handlers/webhooks/3rdparty.go (2)
NewThirdPartyController(32-40)ThirdPartyController(26-30)
internal/sms-gateway/modules/sse/service.go (1)
internal/sms-gateway/modules/sse/metrics.go (1)
ErrorTypeWriteFailure(21-21)
internal/sms-gateway/modules/messages/repository.go (3)
internal/sms-gateway/modules/messages/repository_filter.go (3)
SelectFilter(16-23)SelectOptions(51-62)Order(7-7)internal/sms-gateway/modules/messages/models.go (2)
Message(35-55)ProcessingStatePending(16-16)internal/sms-gateway/modules/messages/errors.go (1)
ErrLockFailed(6-6)
internal/sms-gateway/handlers/mobile.go (1)
internal/sms-gateway/models/models.go (2)
User(17-23)Device(32-43)
internal/sms-gateway/app.go (6)
internal/config/module.go (1)
Module(22-132)internal/sms-gateway/handlers/module.go (1)
Module(15-39)internal/sms-gateway/modules/sse/module.go (1)
Module(10-29)internal/sms-gateway/modules/devices/module.go (1)
Module(16-32)internal/sms-gateway/modules/cleaner/module.go (1)
Module(28-36)internal/sms-gateway/modules/metrics/module.go (1)
Module(9-17)
internal/sms-gateway/handlers/settings/3rdparty.go (5)
internal/sms-gateway/handlers/devices/3rdparty.go (2)
NewThirdPartyController(24-36)ThirdPartyController(18-22)internal/sms-gateway/handlers/logs/3rdparty.go (2)
NewThirdPartyController(24-31)ThirdPartyController(20-22)internal/sms-gateway/handlers/messages/3rdparty.go (2)
NewThirdPartyController(44-53)ThirdPartyController(37-42)internal/sms-gateway/handlers/webhooks/3rdparty.go (2)
NewThirdPartyController(32-40)ThirdPartyController(26-30)internal/sms-gateway/modules/settings/models.go (1)
DeviceSettings(10-17)
internal/sms-gateway/modules/push/fcm/client.go (3)
internal/sms-gateway/modules/push/fcm/errors.go (1)
ErrInitializationFailed(6-6)internal/sms-gateway/modules/push/client/types.go (2)
Client(9-13)Message(15-18)internal/sms-gateway/modules/push/upstream/client.go (1)
Client(22-27)
internal/sms-gateway/handlers/webhooks/3rdparty.go (4)
internal/sms-gateway/handlers/logs/3rdparty.go (2)
NewThirdPartyController(24-31)ThirdPartyController(20-22)internal/sms-gateway/handlers/messages/3rdparty.go (2)
NewThirdPartyController(44-53)ThirdPartyController(37-42)internal/sms-gateway/handlers/settings/3rdparty.go (2)
NewThirdPartyController(35-44)ThirdPartyController(28-33)internal/sms-gateway/modules/webhooks/models.go (1)
Webhook(9-23)
internal/sms-gateway/handlers/messages/3rdparty.go (8)
internal/sms-gateway/handlers/devices/3rdparty.go (2)
NewThirdPartyController(24-36)ThirdPartyController(18-22)internal/sms-gateway/handlers/logs/3rdparty.go (2)
NewThirdPartyController(24-31)ThirdPartyController(20-22)internal/sms-gateway/handlers/settings/3rdparty.go (2)
NewThirdPartyController(35-44)ThirdPartyController(28-33)internal/sms-gateway/handlers/webhooks/3rdparty.go (2)
NewThirdPartyController(32-40)ThirdPartyController(26-30)internal/sms-gateway/handlers/base/handler.go (1)
Handler(15-18)internal/sms-gateway/modules/devices/repository.go (1)
ErrNotFound(13-13)internal/sms-gateway/modules/messages/service.go (1)
EnqueueOptions(27-29)internal/sms-gateway/modules/messages/errors.go (1)
ValidationError(9-9)
internal/sms-gateway/modules/push/types.go (1)
internal/sms-gateway/modules/push/client/types.go (1)
Event(20-23)
internal/sms-gateway/modules/push/service.go (2)
internal/sms-gateway/modules/push/client/types.go (3)
Client(9-13)Event(20-23)Message(15-18)internal/sms-gateway/modules/push/types.go (1)
Event(17-17)
internal/sms-gateway/cache/factory.go (2)
internal/sms-gateway/cache/errors.go (1)
ErrInvalidConfig(6-6)internal/config/config.go (1)
Cache(81-83)
internal/sms-gateway/handlers/health.go (1)
internal/sms-gateway/modules/health/types.go (1)
CheckDetail(51-61)
internal/sms-gateway/handlers/messages/params.go (1)
internal/sms-gateway/modules/messages/repository_filter.go (3)
SelectFilter(16-23)SelectOptions(51-62)Order(7-7)
internal/sms-gateway/handlers/webhooks/mobile.go (4)
internal/sms-gateway/modules/webhooks/service.go (1)
Service(28-37)internal/sms-gateway/handlers/events/mobile.go (2)
NewMobileController(19-27)MobileController(13-17)internal/sms-gateway/handlers/messages/mobile.go (2)
NewMobileController(35-43)MobileController(29-33)internal/sms-gateway/handlers/settings/mobile.go (2)
NewMobileController(23-37)MobileController(16-21)
internal/sms-gateway/modules/push/upstream/client.go (3)
internal/sms-gateway/modules/push/fcm/client.go (2)
New(21-27)Client(14-19)internal/sms-gateway/modules/push/service.go (1)
New(43-73)internal/sms-gateway/modules/push/client/types.go (2)
Client(9-13)Message(15-18)
internal/sms-gateway/handlers/logs/3rdparty.go (4)
internal/sms-gateway/handlers/devices/3rdparty.go (2)
NewThirdPartyController(24-36)ThirdPartyController(18-22)internal/sms-gateway/handlers/messages/3rdparty.go (2)
NewThirdPartyController(44-53)ThirdPartyController(37-42)internal/sms-gateway/handlers/settings/3rdparty.go (2)
NewThirdPartyController(35-44)ThirdPartyController(28-33)internal/sms-gateway/handlers/webhooks/3rdparty.go (2)
NewThirdPartyController(32-40)ThirdPartyController(26-30)
internal/sms-gateway/handlers/messages/mobile.go (5)
internal/sms-gateway/handlers/events/mobile.go (2)
NewMobileController(19-27)MobileController(13-17)internal/sms-gateway/handlers/settings/mobile.go (2)
NewMobileController(23-37)MobileController(16-21)internal/sms-gateway/handlers/webhooks/mobile.go (2)
NewMobileController(21-33)MobileController(15-19)internal/sms-gateway/handlers/base/handler.go (1)
Handler(15-18)internal/sms-gateway/models/models.go (1)
Device(32-43)
internal/sms-gateway/modules/messages/repository_filter.go (2)
internal/sms-gateway/modules/webhooks/repository_filter.go (2)
WithExtID(7-11)WithDeviceID(41-46)internal/sms-gateway/modules/messages/models.go (1)
ProcessingState(12-12)
internal/sms-gateway/modules/events/module.go (2)
internal/sms-gateway/app.go (2)
Module(36-62)Run(64-75)internal/sms-gateway/modules/auth/module.go (1)
Module(10-32)
internal/sms-gateway/modules/auth/service.go (6)
internal/sms-gateway/online/service.go (2)
New(28-37)Service(14-17)internal/sms-gateway/models/models.go (4)
User(17-23)NewUser(25-30)Device(32-43)NewDevice(45-50)internal/sms-gateway/modules/devices/service.go (1)
Service(31-40)internal/sms-gateway/modules/auth/types.go (1)
OneTimeCode(15-18)pkg/crypto/passwords.go (2)
MakeBCryptHash(14-20)CompareBCryptHash(22-27)internal/sms-gateway/modules/auth/errors.go (1)
ErrAuthorizationFailed(6-6)
internal/sms-gateway/modules/messages/service.go (3)
internal/sms-gateway/modules/messages/repository_filter.go (3)
Order(7-7)SelectFilter(16-23)SelectOptions(51-62)internal/sms-gateway/modules/messages/models.go (2)
MessageRecipient(111-117)ProcessingState(12-12)internal/sms-gateway/modules/messages/errors.go (1)
ValidationError(9-9)
🪛 GitHub Check: CodeQL
.github/workflows/pr.yml
[warning] 13-42: Workflow does not contain permissions
Actions job or workflow does not limit the permissions of the GITHUB_TOKEN. Consider setting an explicit permissions block, using the following as a minimal starting point: {{contents: read}}
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (5)
- GitHub Check: E2E
- GitHub Check: Benchmark
- GitHub Check: Test
- GitHub Check: Lint
- GitHub Check: Analyze (go)
🔇 Additional comments (79)
pkg/cache/memory_edge_test.go (1)
252-257: Approved: Multi-line formatting improves readability.The reformatted
t.Logf()call follows idiomatic Go conventions for long function invocations. Breaking arguments across separate lines enhances readability without altering behavior or logic. This aligns well with the broader formatting and linting improvements in the PR.internal/sms-gateway/handlers/upstream.go (1)
97-103: LGTM! Good refactor.Extracting rate limiting values into named constants improves code clarity and maintainability.
internal/sms-gateway/modules/messages/cache.go (3)
30-30: LGTM: Idiomatic parameter naming.The change from
IDtoidfollows Go conventions for unexported parameters.
46-50: LGTM: Improved error wrapping.The explicit error wrapping with context and explicit nil return improve debuggability and code clarity.
53-53: LGTM: Consistent naming and error handling.The parameter naming aligns with the
Setmethod, and the error wrapping provides helpful context. UsingjsonErrappropriately avoids variable shadowing.Also applies to: 57-59, 67-68
internal/sms-gateway/cache/errors.go (1)
5-6: LGTM: Sentinel error for configuration validation.The
ErrInvalidConfigsentinel error follows Go best practices and enables callers to useerrors.Is()for robust error checking.internal/sms-gateway/cache/factory.go (3)
31-31: LGTM: Proper error wrapping with sentinel error.Wrapping URL parse errors with
ErrInvalidConfigenables callers to useerrors.Is()for configuration error checking.
53-53: LGTM: Proper error wrapping with sentinel error.Wrapping invalid scheme errors with
ErrInvalidConfigmaintains consistency with error handling at Line 31.
37-39: No action required. Review comment is incorrect.The memory backend actually creates separate cache instances for each
factory.New()call. Sincecache.NewMemory(0)returns a new*memoryCachewith its ownitemsmap, theeventsandblacklistcaches are completely isolated—no collisions occur.The
nameparameter being ignored is inconsequential here: the Redis backend uses it for prefixing because Redis is a shared singleton service requiring namespace separation. The memory backend achieves the same isolation naturally through separate instances. The suggestedcache.NewMemoryWithPrefixconstructor doesn't exist in the codebase and isn't needed.Likely an incorrect or invalid review comment.
test/e2e/utils_test.go (1)
21-25: LGTM!The multi-line formatting improves readability for this function signature with multiple parameters.
internal/sms-gateway/handlers/root.go (1)
28-28: LGTM!The lint directive is appropriately applied for middleware that propagates errors wrapped elsewhere in the chain.
.gitignore (1)
53-53: LGTM!This correctly implements the macOS Icon file pattern, which requires carriage return characters per the comment above.
internal/sms-gateway/modules/events/metrics.go (1)
8-8: LGTM!The trailing periods improve comment consistency and professionalism throughout the file.
Also applies to: 29-29, 36-36, 60-60, 65-65, 70-70
internal/sms-gateway/modules/webhooks/models.go (1)
10-12: LGTM!The normalized struct tag spacing improves consistency across the model definition.
Also applies to: 16-17
test/e2e/mobile_test.go (1)
193-195: LGTM!The multi-line formatting improves readability of the base64 encoding expression.
internal/sms-gateway/modules/messages/module.go (1)
37-40: Clarify the need forinit()with migration registration.The addition of an
init()function with a side effect (registering a migration) is generally discouraged in Go. Given that this PR is refactoring modules to useModule() fx.Optionpatterns (as seen in other modules), consider registering the migration within theModule()function usingfx.Invokeinstead.Could you clarify:
- What "backward compatibility" this
init()maintains?- Why migration registration cannot be done within the existing
Module()function?internal/sms-gateway/pubsub/module.go (1)
5-5: LGTM!The error wrapping with context follows Go best practices and improves error diagnostics while preserving the error chain.
Also applies to: 23-23
pkg/pubsub/redis.go (2)
89-93: LGTM!The error wrapping improves observability by providing clear context when Redis publish operations fail. The implementation follows Go best practices.
178-180: LGTM!Consistent error wrapping for the Close operation. The conditional wrapping based on
ownedClientis appropriate since we only close the client if we own it.internal/sms-gateway/modules/push/client/types.go (1)
9-13: LGTM!The Client interface provides a clean abstraction with:
- Context-aware methods for proper cancellation and timeout handling
- Granular error handling in Send (per-message errors plus operation-level error)
- Clear lifecycle management with Open/Send/Close
internal/sms-gateway/handlers/middlewares/userauth/userauth.go (1)
118-123: LGTM!The nil check prevents potential nil pointer dereference and provides appropriate error handling when no user is present.
pkg/cache/redis.go (2)
189-190: LGTM!Minor formatting adjustment with no behavioral impact.
257-259: LGTM!Error wrapping is consistent with the pattern in
pkg/pubsub/redis.goand improves observability when Redis client close operations fail.internal/sms-gateway/modules/events/errors.go (1)
1-7: LGTM!Good use of a sentinel error that enables callers to check for validation failures using
errors.Is(). The generic message is appropriate, with specific context added via error wrapping.internal/sms-gateway/modules/settings/models.go (2)
11-12: LGTM!Moving the embedded
TimedModelto the top follows Go conventions and improves readability by separating embedded fields from regular fields.
20-20: LGTM!The change from
&DeviceSettings{}tonew(DeviceSettings)is functionally equivalent and aligns with the pattern ininternal/sms-gateway/modules/messages/models.go.Note: There's inconsistency in migration patterns across modules. For example,
internal/sms-gateway/modules/webhooks/models.go:26uses&Webhook{}. Consider standardizing on one pattern across all modules for consistency.internal/sms-gateway/modules/push/client.go (1)
11-31: LGTM!Clean factory pattern implementation with:
- Proper mode-based client construction
- Clear error messages with context wrapping
- Interface return type for flexibility
- Appropriate handling of invalid modes
internal/sms-gateway/modules/sse/service.go (1)
170-181: LGTM! Improved error handling with explicit flush error capture.The explicit error handling for both write and flush operations, along with proper error wrapping and metrics, is a solid improvement over the previous implementation.
internal/sms-gateway/handlers/base/handler_test.go (1)
6-6: LGTM! Good idiomatic improvements.Using
errors.Newfor simple error strings andhttp.Method*constants instead of string literals improves code clarity and maintainability.Also applies to: 39-39, 46-46, 53-53, 120-120, 123-123, 186-186, 243-243
internal/sms-gateway/handlers/logs/3rdparty.go (1)
24-31: LGTM! Constructor pattern consistent with other handlers.The constructor relocation and explicit dependency injection align well with the broader refactoring pattern seen across other 3rd-party handlers in this PR.
Dockerfile.goreleaser (1)
1-27: LGTM! Well-structured multi-arch Dockerfile.Good security practices with non-root user, proper healthcheck configuration, and correct multi-architecture build support via TARGETPLATFORM.
internal/sms-gateway/handlers/settings/3rdparty.go (1)
35-44: LGTM! Constructor pattern consistent with PR-wide refactoring.The constructor relocation and explicit dependency injection align with the broader pattern applied across other handlers.
internal/sms-gateway/modules/auth/repository.go (1)
19-23: LGTM! Efficient pointer-based API.Returning pointers instead of values is more efficient for structs, and using
new()is idiomatic. The GORM query updates correctly use the pointer without re-referencing.Also applies to: 25-29
.goreleaser.yaml (1)
44-48: No action required—the "latest" tag does not interfere with PR builds.In snapshot mode, GoReleaser creates local images only and appends the platform architecture to tags (e.g.,
latest-arm64,latest-amd64). The PR workflow then manually retags these images with PR-specific identifiers (pr-{PR_NUMBER}-arm64,pr-{PR_NUMBER}-amd64) and pushes only those tags to the registry. The "latest" tag is never pushed during PR builds, so production images are not at risk.internal/sms-gateway/modules/messages/converters.go (1)
13-17: Zero-value TTL init keeps behavior stableDeclaring the pointer nil upfront and only assigning when needed tightens the flow without changing semantics. All good here.
internal/sms-gateway/handlers/events/mobile.go (1)
44-46: Wrapcheck suppression matches handler contractDocumenting that the SSE service wraps its own errors justifies the wrapcheck exemption and keeps lint noise down. Looks tidy.
internal/sms-gateway/modules/events/types.go (1)
29-42: Helpful error context on JSON failuresAdding explicit context to marshal/unmarshal failures will make downstream diagnostics easier with zero behavior change. Nice improvement.
internal/sms-gateway/handlers/devices/3rdparty.go (1)
24-35: Constructor refactor aligns with direct dependency wiringSwitching to explicit parameters while still seeding
base.Handlerwith logger/validator keeps behavior intact and matches the emerging pattern. Looks solid.internal/sms-gateway/models/models.go (1)
25-50: Model helpers make intent explicitThese constructors neatly capture the minimal fields needed when creating users/devices while leaving the rest to be filled by services. No concerns here.
internal/sms-gateway/modules/push/module.go (2)
19-21: LGTM: Provider correctly configured.The
newClientprovider is appropriately scoped as private and follows the standard fx.Provide pattern.
25-34: LGTM: Lifecycle management correctly implemented.The lifecycle hook properly wires the client's Open and Close methods to the application lifecycle using the standard Uber FX pattern.
internal/sms-gateway/handlers/health.go (1)
89-89: LGTM: Unused parameter made explicit.The underscore correctly indicates that the map key is intentionally unused, which is a Go best practice and improves code clarity.
internal/sms-gateway/modules/push/fcm/errors.go (1)
5-7: LGTM: Sentinel error correctly defined.The error follows the standard Go pattern for sentinel errors and is appropriately exported for use with
errors.Is().internal/sms-gateway/modules/auth/errors.go (1)
5-7: LGTM: Sentinel error correctly defined.The error follows the standard Go pattern for sentinel errors and is appropriately exported for use with
errors.Is().internal/sms-gateway/modules/push/fcm/utils.go (1)
7-10: LGTM: Type migration correctly implemented.The change from
types.Eventtoclient.Eventis consistent with the broader refactoring to consolidate types in the client package. The function logic remains correct.internal/sms-gateway/modules/auth/types.go (1)
14-18: LGTM: Type rename improves clarity.The rename from
AuthCodetoOneTimeCodebetter reflects the type's purpose as a one-time authorization code..github/workflows/go.yml (2)
67-67: LGTM: Test caching disabled for coverage.Adding
-count=1ensures fresh test runs for accurate coverage reporting, which is the correct practice for CI coverage collection.
96-129: Review cache key strategy for benchmark history.The restore key (line 102) uses
${{ runner.os }}-benchmark-${{ github.ref_name }}while the save key (line 129) includes an additional${{ github.run_id }}. This creates a unique cache entry for each run, which means:
- The restore step will use the
restore-keysfallback since exact matches won't exist- Multiple cache entries will accumulate per branch
- GitHub Actions will eventually evict older entries due to cache size limits
If the intent is to maintain a single benchmark history per branch, consider removing
github.run_idfrom the save key:- key: ${{ runner.os }}-benchmark-${{ github.ref_name }}-${{ github.run_id }} + key: ${{ runner.os }}-benchmark-${{ github.ref_name }}If accumulating multiple benchmark snapshots per branch is intentional for history tracking, the current approach is acceptable but may lead to cache churn.
internal/sms-gateway/modules/devices/service.go (1)
78-105: LGTM! Well-structured random device selection.The GetAny method implements a clean conditional filtering pattern and handles multiple results appropriately with random selection. The use of
math/rand/v2with thegosecnolint directive is justified for this non-cryptographic device selection use case.internal/sms-gateway/modules/messages/errors.go (1)
3-13: LGTM! Clean error type refactoring.The addition of
ErrLockFailedsentinel error and theValidationErrortype rename maintain clear error semantics. The implementations are correct and align with Go error handling best practices.internal/sms-gateway/modules/auth/module.go (1)
10-32: LGTM! Consistent module factory pattern.The conversion from a module variable to a
Module()factory function is a clean refactor that aligns with the PR objective. The internal wiring remains unchanged, and the pattern is consistently applied across multiple modules.internal/sms-gateway/app.go (3)
36-62: LGTM! Module wiring properly updated.The conversion to
Module()factory function is clean and consistent. The updated call sites correctly useModule()for modules that have been converted to functions, while maintaining direct references for modules that remain as variables.
65-65: LGTM! Appropriate nolint directive.The
reassignnolint is justified here ascli.DefaultCommandis a framework-specific configuration that needs to be set at startup.
136-136: LGTM! Justified gochecknoinits directive.The nolint directive with "backward compatibility" reason is appropriate for this
init()function that registers the CLI command.internal/sms-gateway/modules/messages/models.go (3)
36-37: LGTM! Soft delete capability added.The embedding of
SoftDeletableModeladds soft deletion tracking to messages, which is a useful feature for maintaining historical data.
71-71: LGTM! Appropriate nilnil directives.The
nilnilnolint directives are justified here. Returning(nil, nil)is intentional to distinguish "wrong message type" from error conditions, which is a valid pattern for type-specific getters.Also applies to: 98-98
137-141: LGTM! Improved error handling.The migration function now uses
fmt.Errorfwith%wfor proper error wrapping, which improves error context and enables error unwrapping.internal/sms-gateway/modules/settings/module.go (2)
9-23: LGTM! Consistent module factory pattern.The conversion to
Module()factory function follows the same pattern applied across the codebase, maintaining consistency in module wiring.
25-25: LGTM! Justified gochecknoinits directive.The nolint directive for the migration registration init function is appropriate and consistent with similar patterns in the codebase.
internal/sms-gateway/modules/events/module.go (1)
10-39: LGTM! Improved error handling in shutdown path.The conversion to
Module()factory function is consistent with the broader refactor. Additionally, the use ofshErrto capture and log shutdown errors (line 25-26) is an improvement over direct error checking, providing better observability when graceful shutdown fails.internal/sms-gateway/handlers/converters/messages.go (1)
27-28: DeviceID initialization is correct.The
DeviceIDfield is intentionally set to""because the source structmessages.MessageOutdoes not have aDeviceIDfield. The domain layer separates message content (MessageOut) from device state information (MessageStateOut, which includes DeviceID). This is an architectural choice where the message converter receives only message data without device context. No action needed.internal/sms-gateway/modules/events/service.go (1)
55-57: Nice validation guard.Wrapping the empty event-type check with
ErrValidationFailedkeeps the contract explicit and prevents meaningless messages from hitting downstream transports.internal/sms-gateway/openapi/docs.go (1)
789-865: Health probe docs synced with implementation.The new liveness/readiness/startup endpoints and 503 semantics make the spec match Kubernetes expectations, which should cut down on operator confusion. Nicely done.
internal/sms-gateway/modules/push/types.go (1)
21-44: Serialization error wrapping is on point.Routing errors through
fmt.Errorf(... %w ...)gives callers stackable context while keeping the payload format untouched. Good adjustment alongside the new client.Event alias.internal/sms-gateway/modules/push/fcm/client.go (1)
37-86: Great consistency on initialization and message typing.The unified
ErrInitializationFailedwrapping and the move toclient.Messagekeep the FCM client aligned with the new push abstraction while preserving caller ergonomics..github/workflows/release.yml (4)
1-10: LGTM! Workflow trigger and permissions correctly configured for GoReleaser.The migration from release event to tag-based trigger (v*) and the addition of
packages: writepermission are appropriate for GoReleaser's workflow, which creates releases on tag push and can publish Docker images to GHCR.
56-88: LGTM! Deploy job correctly wired to goreleaser.The dependency change from
buildtogoreleaserand the APP_VERSION extraction logic are correct. The version is properly extracted from the git ref and passed to Terraform for deployment.
99-99: LGTM! Secondary deployment correctly depends on goreleaser.The dependency update is consistent with the primary deployment job.
35-36: Clarify the purpose of the hardcoded date "2022-06-15" in the RELEASE_ID calculation.The date appears nowhere else in the codebase and lacks documentation. Confirm whether this represents a meaningful project milestone (e.g., inception date) or if it's legacy code that should be updated. This ensures the versioning scheme remains intentional and maintainable.
internal/sms-gateway/handlers/messages/params.go (1)
32-86: LGTM! Type renames are consistent and align with module refactoring.The removal of the "Messages" prefix from type names (e.g.,
MessagesSelectFilter→SelectFilter) improves code clarity since these types are already in themessagespackage namespace. All usages have been updated consistently throughout the file.internal/sms-gateway/handlers/messages/3rdparty.go (5)
44-53: LGTM! Constructor follows consistent dependency injection pattern.The new constructor properly initializes the base Handler and wires dependencies, consistent with other controllers in the codebase.
84-101: LGTM! Device selection logic simplified with better error handling.The migration to
GetAnymethod improves code clarity and the error handling properly distinguishes between user errors (no device found) and system errors, with appropriate logging for troubleshooting.
189-204: LGTM! Parameter parsing improved with consistent error handling.The switch to pointer-based parameter parsing and the addition of error logging improve code maintainability and debugging capabilities.
250-269: LGTM! Consistent error handling improvements throughout.The error handling enhancements with logging and standardized HTTP error responses improve both user experience and system observability.
133-137: No changes required.The device parameter is correctly passed by value. The
Enqueuemethod signature expectsdevice models.Device(value type), and the call site correctly dereferences the pointer to pass the value (*device).internal/sms-gateway/handlers/messages/mobile.go (2)
58-63: LGTM! Parameter parsing improved with consistent error handling.The pointer-based parsing and error wrapping align with the error handling improvements throughout the PR.
93-97: LGTM! Consistent error handling.The error wrapping with
fiber.NewErrorstandardizes HTTP error responses.internal/sms-gateway/handlers/settings/mobile.go (2)
23-37: LGTM! Constructor refactored for explicit dependency injection.The migration from fx.In-based parameters to explicit constructor arguments improves testability and reduces framework coupling, aligning with the pattern used in
handlers/events/mobile.goandhandlers/webhooks/mobile.go.
50-54: LGTM! Enhanced error context for debugging.The addition of device ID and user ID to the error message improves troubleshooting capabilities.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 2
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
internal/sms-gateway/modules/settings/repository.go (1)
47-48: Preservecreated_atwhen upserting.DeviceSettings embeds
models.TimedModel, so callingCreatewithclause.OnConflict{UpdateAll: true}rewrites every column on conflict — includingcreated_at. On everyUpdateSettingsrun for an existing row we now blow away the original creation timestamp with the freshly generated value (or whatever the caller sent), erasing history. Copy the original timestamps fromsourcebefore the upsert, or narrow the conflict handler to only mutate the intended fields (e.g.,settings,updated_at). For example:- if err := tx.Clauses(clause.OnConflict{UpdateAll: true}).Create(settings).Error; err != nil { + if !source.CreatedAt.IsZero() { + settings.CreatedAt = source.CreatedAt + } + + if err := tx.Clauses(clause.OnConflict{UpdateAll: true}).Create(settings).Error; err != nil { return err }Either approach prevents clobbering immutable timestamps.
🧹 Nitpick comments (3)
.github/workflows/pr.yml (2)
24-24: Inconsistent GitHub Actions version pinning across jobs.The e2e job uses
actions/setup-go@v5(line 24) while the goreleaser job usesactions/setup-go@v6(line 73). For consistency and predictability, use the same major version across both jobs unless there's a specific reason requiring different versions.Consider standardizing on
@v6across both jobs:- name: Set up Go uses: actions/setup-go@v5 + uses: actions/setup-go@v6Also applies to: 73-73
142-149: Hardcoded artifact domain limits flexibility.The PR comment table hardcodes
https://github.capcom.me/for artifact URLs (lines 142-149), but the S3 endpoint is already captured in${{ secrets.AWS_ENDPOINT }}(line 99). Using a hardcoded domain reduces flexibility and makes the workflow less portable if the domain or endpoint changes.Parameterize the artifact base URL using a GitHub variable or derive it from the endpoint. For example:
- | 🍎 Darwin arm64 | [${{ env.PROJECT_NAME }}_Darwin_arm64.tar.gz](https://github.capcom.me/${{ github.repository }}/${{ github.event.pull_request.head.sha }}/${{ env.PROJECT_NAME }}_Darwin_arm64.tar.gz) | + | 🍎 Darwin arm64 | [${{ env.PROJECT_NAME }}_Darwin_arm64.tar.gz](${{ vars.ARTIFACT_BASE_URL }}/${{ github.repository }}/${{ github.event.pull_request.head.sha }}/${{ env.PROJECT_NAME }}_Darwin_arm64.tar.gz) |Define
ARTIFACT_BASE_URLas a GitHub Actions repository variable pointing to your CDN or S3 web endpoint.internal/sms-gateway/handlers/upstream.go (1)
97-103: Consider making rate limits configurable.The constants improve readability, but rate limits are operational parameters that may need different values across environments (dev/staging/production). Since the
Configstruct already controls upstream behavior (line 91), consider addingUpstreamRateLimitandUpstreamRateWindowfields there.Example addition to
Config:type Config struct { UpstreamEnabled bool UpstreamRateLimit int // defaults to 5 UpstreamRateWindow time.Duration // defaults to 60s // ... other fields }Then use them here:
- const ( - rateLimit = 5 - rateTime = 60 * time.Second - ) router.Post("/push", limiter.New(limiter.Config{ - Max: rateLimit, - Expiration: rateTime, + Max: h.config.UpstreamRateLimit, + Expiration: h.config.UpstreamRateWindow, LimiterMiddleware: limiter.SlidingWindow{}, }), h.postPush)
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (7)
.github/workflows/pr.yml(1 hunks)internal/sms-gateway/handlers/messages/params.go(4 hunks)internal/sms-gateway/handlers/middlewares/userauth/userauth.go(2 hunks)internal/sms-gateway/handlers/mobile.go(7 hunks)internal/sms-gateway/handlers/upstream.go(2 hunks)internal/sms-gateway/modules/messages/cache.go(2 hunks)internal/sms-gateway/modules/settings/repository.go(5 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
- internal/sms-gateway/handlers/mobile.go
🧰 Additional context used
🧬 Code graph analysis (4)
internal/sms-gateway/modules/settings/repository.go (1)
internal/sms-gateway/modules/settings/models.go (1)
DeviceSettings(10-17)
internal/sms-gateway/modules/messages/cache.go (2)
internal/sms-gateway/modules/messages/domain.go (1)
MessageStateOut(38-44)pkg/cache/options.go (2)
WithTTL(22-30)AndSetTTL(64-68)
internal/sms-gateway/handlers/messages/params.go (1)
internal/sms-gateway/modules/messages/repository_filter.go (3)
SelectFilter(16-23)SelectOptions(51-62)Order(7-7)
internal/sms-gateway/handlers/middlewares/userauth/userauth.go (1)
internal/sms-gateway/models/models.go (1)
User(17-23)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (4)
- GitHub Check: E2E
- GitHub Check: Benchmark
- GitHub Check: Lint
- GitHub Check: Analyze (go)
🔇 Additional comments (12)
.github/workflows/pr.yml (2)
15-16: E2E job permissions now properly scoped.The explicit
permissions: contents: readblock correctly addresses the security concern raised in previous reviews. This minimal permission set is appropriate for the checkout and Go setup operations performed in the e2e job.
47-50: GoReleaser job permissions are properly scoped.The permissions block correctly grants minimal access:
contents: readfor checkout,pull-requests: writefor commenting, andpackages: writefor pushing to GitHub Container Registry. This aligns with security best practices.internal/sms-gateway/modules/messages/cache.go (2)
30-51: LGTM! Improved error handling.The explicit error wrapping with descriptive messages and the clear nil return enhance code maintainability and debugging.
53-72: LGTM! Appropriate nil handling and error wrapping.The improved error messages provide better debugging context. The
//nolint:nilnilannotation on line 63 is correctly applied—returning(nil, nil)is intentional here to distinguish between a cache miss and an error, with the empty value representing a cached "Not Found" state.internal/sms-gateway/handlers/messages/params.go (6)
10-13: Verify the type change from uint to int for DeviceActiveWithin.The
DeviceActiveWithinfield was changed fromuinttoint. While the validationmin=1prevents negative values,uintis semantically more appropriate for duration/count values. Please confirm this change is intentional and doesn't introduce any compatibility issues with existing consumers.
15-22: LGTM!The formatting alignment of validation tags improves readability without changing functionality.
32-56: LGTM!The return type update from
MessagesSelectFiltertoSelectFilteraligns with the broader type renaming refactoring. The method logic remains correct.
58-76: LGTM!The return type update to
SelectOptionsis consistent with the refactoring. The introduction of themaxLimitconstant provides defensive programming, even though Line 20's validation already enforcesmax=100. This redundancy is acceptable as a safety measure.
78-80: LGTM!The
Orderfield type update fromMessagesOrdertoOrderis consistent with the type renaming refactoring.
82-87: LGTM!The return type update to
messages.Orderis correct and maintains the proper default behavior.internal/sms-gateway/handlers/upstream.go (1)
67-69: LGTM! Validation error status code corrected.The change from
fiber.StatusBadGateway(502) tofiber.StatusBadRequest(400) correctly maps validation failures to client errors. This addresses the previous review feedback.internal/sms-gateway/handlers/middlewares/userauth/userauth.go (1)
94-100: LGTM: Safe pointer-based user retrieval.The refactored
GetUsercorrectly performs a safe type assertion and returns nil when the user is not present or has an unexpected type. This avoids panics and aligns with the broader shift to pointer-based user handling.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 1
♻️ Duplicate comments (1)
.github/workflows/pr.yml (1)
92-92: Pin the custom S3 upload action to a specific version.Line 92 references
capcom6/upload-s3-action@master, which uses the unpinned master branch. This introduces supply-chain risk and could break if the action changes unexpectedly. Pin to a specific release tag or commit SHA to eliminate this risk.Apply this diff:
- uses: capcom6/upload-s3-action@master + uses: capcom6/upload-s3-action@v1(Replace
v1with the appropriate stable version tag you intend to use.)
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (1)
.github/workflows/pr.yml(1 hunks)
🔇 Additional comments (2)
.github/workflows/pr.yml (2)
15-16: Explicit permissions block added to e2e job.The e2e job now correctly specifies minimal required permissions. This addresses the prior security finding and aligns with GitHub's least-privilege best practices.
44-50: Goreleaser job permissions and dependencies configured appropriately.The goreleaser job correctly specifies permissions (contents: read, pull-requests: write, packages: write), depends on e2e job completion, and uses conditional steps to skip operations for dependabot. The setup aligns with the GoReleaser snapshot distribution workflow.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 0
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (6)
pkg/cache/options.go (1)
20-28: Fix logic bug: zero-time assignment is immediately overwritten.Lines 22-24 set a zero time when
ttl <= 0, but line 26 unconditionally overwrites it withtime.Now().Add(ttl). The early-return or else-block is missing.Note: This is a pre-existing bug not introduced by this PR, but it should be fixed to prevent incorrect expiry behavior.
Apply this diff to fix the logic:
func WithTTL(ttl time.Duration) Option { return func(o *options) { if ttl <= 0 { o.validUntil = time.Time{} + return } o.validUntil = time.Now().Add(ttl) } }pkg/cache/memory_profile_test.go (1)
123-143: Restore standard counting loop for integer constants
for i := range numCachesdoes not compile becauserangeexpects an array, slice, map, string, or channel. WithnumCachesdefined as an int constant, this turns the whole test file into a build breaker. Please revert to the classic counting loop so the code builds.- for i := range numCaches { + for i := 0; i < numCaches; i++ {pkg/cache/redis.go (1)
43-46: Fix HTTL update when the field has no TTL
HTTLreturns-1when the hash field has no TTL. AddingttlDeltaon top of-1means a 1-second extension turns into0, expiring the item immediately. That regression breaksGet(..., cache.WithTTLUpdate(...))for keys that previously had no TTL. Clamp negative TTLs to zero before adding the delta so the extension behaves correctly.- local ttl = redis.call('HTTL', KEYS[1], field) - local newTtl = ttl + ttlDelta + local ttl = redis.call('HTTL', KEYS[1], field) + if ttl < 0 then + ttl = 0 + end + local newTtl = ttl + ttlDeltapkg/cache/memory_bench_test.go (3)
286-290: Revert to counting loop for benchmark prefill
for i := range bm.operationsPerGoroutine * bm.goroutineswill not compile becauserangecannot iterate over an integer expression. Please switch back to the explicit counter so the benchmark builds.- for i := range bm.operationsPerGoroutine * bm.goroutines { + for i := 0; i < bm.operationsPerGoroutine*bm.goroutines; i++ {
394-398: Fix invalid range over scalarsizeHere too,
rangeon anintis invalid Go. Use the classicfor j := 0; j < size; j++form so the benchmark compiles.- for j := range size { + for j := 0; j < size; j++ {
453-457: Restore integer-count loop for cold-key prepopulation
for i := range numKeysis a compile-time error. Please reinstate the counted loop.- for i := range numKeys { + for i := 0; i < numKeys; i++ {
🧹 Nitpick comments (3)
.golangci.yml (3)
159-166: Clarify cyclop complexity thresholds.
max-complexity: 30is quite permissive for a "very strict" config (line 7), especially paired withpackage-average: 10.0. This creates tension between per-function and package-level enforcement. Consider whether both thresholds align with your actual code quality goals, or ifmax-complexityshould be lowered to catch outliers more aggressively.
234-282: Review exhaustruct exclusion list for project specificity.The
exhaustruct.excludelist contains many third-party library patterns (Firebase, telebot, S3, Prometheus, etc.) that suggest this config was borrowed from another project. If this PR introduces stricter linting project-wide, verify that:
- These exclusions actually correspond to types used in this codebase.
- The exclusions don't inadvertently suppress legitimate struct initialization bugs.
- Project maintainers understand why each exclusion exists.
Consider adding a brief comment in the config (or a separate ADR) explaining the rationale for the specific exclusion patterns.
372-375: Reconsidernakedretmax-func-lines: 0.Setting
max-func-lines: 0means all naked returns are flagged, regardless of function length. This is extremely strict and will likely generate many issues across the codebase. Standard practice is to enforce naked returns only in shorter functions (typical defaults are 15–30 lines).Verify that this setting aligns with the team's code review standards, or consider raising it to a reasonable threshold.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (10)
.golangci.yml(1 hunks)pkg/cache/errors.go(1 hunks)pkg/cache/memory.go(12 hunks)pkg/cache/memory_bench_test.go(19 hunks)pkg/cache/memory_concurrency_test.go(9 hunks)pkg/cache/memory_edge_test.go(8 hunks)pkg/cache/memory_profile_test.go(2 hunks)pkg/cache/memory_test.go(8 hunks)pkg/cache/options.go(1 hunks)pkg/cache/redis.go(14 hunks)
🧰 Additional context used
🧠 Learnings (2)
📚 Learning: 2025-09-10T11:11:43.462Z
Learnt from: capcom6
Repo: android-sms-gateway/server PR: 178
File: pkg/cache/memory.go:59-74
Timestamp: 2025-09-10T11:11:43.462Z
Learning: In pkg/cache/memory.go, the cleanup method removes expired items first, then executes the provided callback function. This means methods like Drain that use cleanup don't need additional expiration filtering since expired items are already removed before the callback runs.
Applied to files:
pkg/cache/memory_concurrency_test.gopkg/cache/memory_test.gopkg/cache/memory_edge_test.gopkg/cache/memory_profile_test.gopkg/cache/redis.gopkg/cache/memory.gopkg/cache/memory_bench_test.go
📚 Learning: 2025-09-10T23:53:42.006Z
Learnt from: capcom6
Repo: android-sms-gateway/server PR: 178
File: pkg/cache/redis.go:63-75
Timestamp: 2025-09-10T23:53:42.006Z
Learning: In pkg/cache/redis.go, the Redis cache implementation uses Redis's native field-level TTL via HExpireAt, meaning expired fields are automatically removed by Redis itself. This differs from the memory implementation where the application tracks expiration times. As a result, operations like Drain that call HGetAll will only return non-expired items since expired fields are already removed by Redis.
Applied to files:
pkg/cache/memory_edge_test.gopkg/cache/redis.gopkg/cache/memory.go
🧬 Code graph analysis (6)
pkg/cache/memory_concurrency_test.go (1)
pkg/cache/errors.go (2)
ErrKeyNotFound(9-9)ErrKeyExists(13-13)
pkg/cache/memory_test.go (1)
pkg/cache/errors.go (2)
ErrKeyNotFound(9-9)ErrKeyExists(13-13)
pkg/cache/memory_edge_test.go (1)
pkg/cache/errors.go (2)
ErrKeyExpired(11-11)ErrKeyNotFound(9-9)
pkg/cache/redis.go (4)
pkg/pubsub/redis.go (2)
NewRedis(42-76)RedisConfig(15-27)pkg/cache/errors.go (1)
ErrInvalidConfig(7-7)pkg/cache/options.go (3)
GetOption(46-46)AndDelete(86-90)Option(6-6)pkg/cache/cache.go (1)
Cache(5-40)
pkg/cache/memory.go (2)
pkg/cache/options.go (3)
GetOption(46-46)AndDelete(86-90)Option(6-6)pkg/cache/cache.go (1)
Cache(5-40)
pkg/cache/memory_bench_test.go (1)
pkg/cache/memory.go (1)
NewMemory(16-23)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (5)
- GitHub Check: Lint
- GitHub Check: Benchmark
- GitHub Check: Test
- GitHub Check: E2E
- GitHub Check: Analyze (go)
🔇 Additional comments (6)
.golangci.yml (2)
309-321: Verify gocritic settings impact on code patterns.Line 317:
paramsOnly: falseforcaptLocaland line 321:skipRecvDeref: falseforunderefare non-default and may flag many existing patterns. Ensure the team has reviewed the warnings these generate before merging, or plan remediation ahead of time.
4-4: I need more specific information about the linters mentioned in the review. Let me search for details on those specific linters in v2.5.0.Need to verify the actual
.golangci.ymlconfiguration file content to assess the full scope of the review comment. Let me search for information about it.Based on my verification, I need to provide the rewritten review comment:
Review comment is incorrect—all mentioned linters are available in golangci-lint v2.5.0.
The linters
copyloopvar(updated to 1.1.0) andtestifylint(updated to 1.3.0) are confirmed in v2.5.0's changelog, and bothtestableexamplesandtestifylintare listed among golangci-lint's supported linters.While
copyloopvarrequires Go 1.22+, golangci-lint automatically disables it if the project's Go version is lower—this is expected behavior, not a compatibility error. The configuration is valid as-is.Likely an incorrect or invalid review comment.
pkg/cache/options.go (1)
12-16: I need to verify that the chaining calls are on differentapply()methods (on filter types, not the options type from the cache package).I need to verify that the chaining calls are on different types. Let me check the repository structure:
Based on my analysis of the script output and web search results, I can now confirm that the chaining calls are on different types (filter objects for GORM queries) and have different signatures than the
options.apply()method inpkg/cache/options.go.All call sites in the cache package (
pkg/cache/memory.go,pkg/cache/redis.go) useapply()without chaining, which is correct for the new void signature.No breaking changes detected.
LGTM!
The removal of the return value is correct since the receiver is a pointer and modifications are applied in-place. All cache package call sites correctly use
apply()as a void call (lines 92, 155 inpkg/cache/memory.goand lines 147, 216, 247 inpkg/cache/redis.go). Chaining calls found in other modules use differentapply()methods on filter types, not on the options type.pkg/cache/memory_concurrency_test.go (3)
5-5: Excellent error handling modernization.The switch to
errors.Is()for sentinel error checks (instead of direct equality) follows Go best practices and makes the code robust against future error wrapping. The consistent application across all tests is well done.Also applies to: 118-118, 224-224, 298-298, 345-345, 380-380, 393-393
32-32: Good code quality improvements.The modern
for range numGoroutinessyntax is cleaner, and renaming togetErrproperly avoids variable shadowing in the closure.Also applies to: 37-39
284-284: Proper unused parameter handling.Using blank identifiers for genuinely unused parameters follows Go conventions and improves code clarity.
Also applies to: 399-399
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 1
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
pkg/cache/redis.go (1)
168-187: Critical: Delete-only operations broken by early return in default case.The switch statement's default case (lines 177-187) returns early, preventing the Lua script from executing when
AndDelete()is used without TTL options. This breaks delete functionality for such calls.Example failure scenario:
// This will NOT delete the key! value, err := cache.Get(ctx, key, AndDelete())The code flow:
o.delete = trueis set byAndDelete()o.isEmpty()returns false (line 152), skipping simple get- No TTL cases match in the switch
- Default case executes simple
HGetand returns (line 186)- Lua script with delete logic (lines 195-196) is never reached
Apply this diff to fix the logic:
var ttlTimestamp, ttlDelta int64 switch { case o.validUntil != nil: ttlTimestamp = o.validUntil.Unix() case o.setTTL != nil: ttlTimestamp = time.Now().Add(*o.setTTL).Unix() case o.updateTTL != nil: ttlDelta = int64(o.updateTTL.Seconds()) case o.defaultTTL: ttlTimestamp = time.Now().Add(r.ttl).Unix() - default: - // No TTL options, fallback to simple get - val, err := r.client.HGet(ctx, r.key, key).Result() - if err != nil { - if errors.Is(err, redis.Nil) { - return nil, ErrKeyNotFound - } - return nil, fmt.Errorf("can't get cache item: %w", err) - } - return []byte(val), nil } delArg := "0"This allows the code to fall through to the Lua script invocation, which correctly handles delete-only operations when
ttlTimestampandttlDeltaare both zero.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (2)
pkg/cache/options.go(2 hunks)pkg/cache/redis.go(15 hunks)
🧰 Additional context used
🧠 Learnings (3)
📚 Learning: 2025-09-10T23:53:42.006Z
Learnt from: capcom6
Repo: android-sms-gateway/server PR: 178
File: pkg/cache/redis.go:63-75
Timestamp: 2025-09-10T23:53:42.006Z
Learning: In pkg/cache/redis.go, the Redis cache implementation uses Redis's native field-level TTL via HExpireAt, meaning expired fields are automatically removed by Redis itself. This differs from the memory implementation where the application tracks expiration times. As a result, operations like Drain that call HGetAll will only return non-expired items since expired fields are already removed by Redis.
Applied to files:
pkg/cache/redis.go
📚 Learning: 2025-09-10T23:54:16.828Z
Learnt from: capcom6
Repo: android-sms-gateway/server PR: 178
File: pkg/cache/redis.go:118-121
Timestamp: 2025-09-10T23:54:16.828Z
Learning: Redis 7.4.0 introduced HEXPIRE, HEXPIREAT, and related commands that support per-field TTL on hash data structures. go-redis v9.6.0+ includes support for these commands with methods like HExpireAt.
Applied to files:
pkg/cache/redis.go
📚 Learning: 2025-09-10T11:11:43.462Z
Learnt from: capcom6
Repo: android-sms-gateway/server PR: 178
File: pkg/cache/memory.go:59-74
Timestamp: 2025-09-10T11:11:43.462Z
Learning: In pkg/cache/memory.go, the cleanup method removes expired items first, then executes the provided callback function. This means methods like Drain that use cleanup don't need additional expiration filtering since expired items are already removed before the callback runs.
Applied to files:
pkg/cache/redis.go
🧬 Code graph analysis (1)
pkg/cache/redis.go (3)
pkg/pubsub/redis.go (2)
NewRedis(42-76)RedisConfig(15-27)pkg/cache/errors.go (1)
ErrInvalidConfig(7-7)pkg/cache/cache.go (1)
Cache(5-40)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
- GitHub Check: Benchmark
- GitHub Check: E2E
- GitHub Check: Analyze (go)
🔇 Additional comments (7)
pkg/cache/redis.go (7)
5-5: LGTM: Proper error handling dependency added.The
errorsimport is needed for theerrors.Is()calls used to detectredis.Nilerrors.
44-46: LGTM: Correct handling of negative TTL values.Setting
ttl = 0whenHTTLreturns a negative value (key doesn't exist or has no expiry) prevents issues when adding the delta.
72-72: LGTM: Type properly exported.The
RedisCachetype is now exported, making it accessible to external consumers. All related signatures and type assertions are correctly updated.Also applies to: 81-81, 100-100, 271-271
87-87: LGTM: Proper sentinel error wrapping.Using
ErrInvalidConfigallows callers to programmatically detect configuration errors.
136-137: LGTM: Self-documenting constant improves readability.The
itemsPerKeyconstant clarifies that HGETALL returns key-value pairs in a flat array.
156-157: LGTM: Proper sentinel error checking.Using
errors.Is()to check forredis.Nilis the correct approach and handles wrapped errors properly.Also applies to: 181-182
263-265: LGTM: Proper error propagation in Close.The error from
client.Close()is now properly wrapped with a descriptive message.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 1
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (1)
pkg/cache/options.go(1 hunks)
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2025-09-10T23:53:42.006Z
Learnt from: capcom6
Repo: android-sms-gateway/server PR: 178
File: pkg/cache/redis.go:63-75
Timestamp: 2025-09-10T23:53:42.006Z
Learning: In pkg/cache/redis.go, the Redis cache implementation uses Redis's native field-level TTL via HExpireAt, meaning expired fields are automatically removed by Redis itself. This differs from the memory implementation where the application tracks expiration times. As a result, operations like Drain that call HGetAll will only return non-expired items since expired fields are already removed by Redis.
Applied to files:
pkg/cache/options.go
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
- GitHub Check: Benchmark
- GitHub Check: E2E
- GitHub Check: Analyze (go)
🔇 Additional comments (1)
pkg/cache/options.go (1)
12-16: LGTM! Simplified apply signature.The removal of the return value from
applysimplifies the API. Since this is an internal method and the PR compiles, all call sites have been appropriately updated.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 0
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (2)
pkg/pubsub/redis.go (1)
169-186: GuardCloseagainst concurrent double-close panics.Two goroutines calling
Closeat once can both fall through the non-blockingselectand executeclose(r.closeCh), triggering a panic. Use the existing mutex to serialize the close operation before releasing it for the wait and client teardown.Apply this diff:
func (r *RedisPubSub) Close() error { - select { - case <-r.closeCh: - return nil - default: - close(r.closeCh) - } + r.mu.Lock() + select { + case <-r.closeCh: + r.mu.Unlock() + return nil + default: + close(r.closeCh) + } + r.mu.Unlock() r.wg.Wait() if r.ownedClient { if err := r.client.Close(); err != nil {pkg/pubsub/memory.go (1)
147-158: SynchronizeCloseto avoid double closingcloseCh.Concurrent callers can both take the
defaultbranch and closem.closeCh, resulting in a panic. Grab the mutex before the check/close, release it before waiting on the subscriber goroutines.Apply this diff:
func (m *MemoryPubSub) Close() error { - select { - case <-m.closeCh: - return nil - default: - } - close(m.closeCh) + m.mu.Lock() + select { + case <-m.closeCh: + m.mu.Unlock() + return nil + default: + close(m.closeCh) + } + m.mu.Unlock() m.wg.Wait() return nil
♻️ Duplicate comments (1)
pkg/cache/options.go (1)
22-29: Clarify the intended semantics for non-positive TTL values.The current implementation has ambiguous semantics:
ttl == 0: setsvalidUntilto the zero valuetime.Time{}, which typically means "no expiry"ttl < 0: setsvalidUntiltotime.Now(), intended to expire immediately but won't work correctly due to comparison logic (as flagged in past reviews)Please clarify the intended behavior:
- Should
ttl == 0mean "no expiry" or "expire immediately"?- Should
ttl < 0be rejected, or should it mean something specific?The comparison bug for
ttl < 0has been extensively documented in previous review comments, which provide detailed analysis and fixes.
🧹 Nitpick comments (3)
internal/sms-gateway/handlers/messages/params.go (2)
12-12: Consider usinguintfor semantic correctness.Since the validation ensures
min=1, usinguintwould be more semantically appropriate to express that only non-negative values are valid. However, if downstream dependencies requireint, this change is acceptable.
26-26: Fix grammatical error in comment.The comment has a grammatical error. It should read "won't be used directly" instead of "won't used directly".
Apply this diff:
- return errors.New("`from` date must be before `to` date") //nolint:err113 // won't used directly + return errors.New("`from` date must be before `to` date") //nolint:err113 // won't be used directlyinternal/sms-gateway/modules/health/types.go (1)
41-51: Consider removing or documenting the unreachable default case.The switch covers all possible
statusLevelvalues (levelPass, levelWarn, levelFail), making the default case at line 50 unreachable. You could either remove it or add a comment explaining it serves as a defensive fallback.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (19)
internal/sms-gateway/app.go(2 hunks)internal/sms-gateway/handlers/messages/params.go(3 hunks)internal/sms-gateway/modules/db/health.go(1 hunks)internal/sms-gateway/modules/health/health.go(2 hunks)internal/sms-gateway/modules/health/module.go(2 hunks)internal/sms-gateway/modules/health/service.go(3 hunks)internal/sms-gateway/modules/health/types.go(3 hunks)internal/sms-gateway/modules/messages/service_test.go(0 hunks)internal/sms-gateway/modules/webhooks/errors.go(3 hunks)internal/sms-gateway/modules/webhooks/models.go(1 hunks)internal/sms-gateway/modules/webhooks/module.go(1 hunks)internal/sms-gateway/modules/webhooks/repository.go(2 hunks)internal/sms-gateway/modules/webhooks/repository_filter.go(1 hunks)internal/sms-gateway/modules/webhooks/service.go(2 hunks)pkg/cache/options.go(1 hunks)pkg/pubsub/memory.go(8 hunks)pkg/pubsub/options.go(1 hunks)pkg/pubsub/pubsub.go(1 hunks)pkg/pubsub/redis.go(6 hunks)
💤 Files with no reviewable changes (1)
- internal/sms-gateway/modules/messages/service_test.go
🧰 Additional context used
🧠 Learnings (2)
📚 Learning: 2025-09-10T23:45:45.502Z
Learnt from: capcom6
Repo: android-sms-gateway/server PR: 178
File: internal/sms-gateway/online/module.go:14-16
Timestamp: 2025-09-10T23:45:45.502Z
Learning: fx.Decorate in uber-go/fx creates module-scoped decorated dependencies, not global modifications. Using fx.Decorate(func(log *zap.Logger) *zap.Logger { return log.Named("module_name") }) is a correct pattern for creating module-specific loggers that are scoped only to that module's dependency graph.
Applied to files:
internal/sms-gateway/modules/webhooks/module.go
📚 Learning: 2025-09-10T23:53:42.006Z
Learnt from: capcom6
Repo: android-sms-gateway/server PR: 178
File: pkg/cache/redis.go:63-75
Timestamp: 2025-09-10T23:53:42.006Z
Learning: In pkg/cache/redis.go, the Redis cache implementation uses Redis's native field-level TTL via HExpireAt, meaning expired fields are automatically removed by Redis itself. This differs from the memory implementation where the application tracks expiration times. As a result, operations like Drain that call HGetAll will only return non-expired items since expired fields are already removed by Redis.
Applied to files:
pkg/cache/options.go
🧬 Code graph analysis (12)
internal/sms-gateway/modules/webhooks/module.go (3)
internal/sms-gateway/app.go (1)
Module(36-62)internal/sms-gateway/modules/health/module.go (1)
Module(8-22)internal/sms-gateway/modules/db/module.go (1)
Module(12-22)
internal/sms-gateway/modules/webhooks/repository.go (1)
internal/sms-gateway/modules/webhooks/models.go (1)
Webhook(11-25)
internal/sms-gateway/modules/health/service.go (1)
internal/sms-gateway/modules/health/types.go (3)
Provider(69-75)Checks(67-67)CheckResult(21-24)
pkg/pubsub/memory.go (2)
pkg/pubsub/options.go (1)
Option(3-3)pkg/pubsub/pubsub.go (2)
Subscription(19-24)PubSub(46-51)
pkg/pubsub/redis.go (2)
pkg/cache/redis.go (2)
NewRedis(81-108)RedisConfig(56-70)pkg/pubsub/pubsub.go (3)
ErrInvalidConfig(9-9)Subscription(19-24)PubSub(46-51)
internal/sms-gateway/modules/webhooks/models.go (3)
internal/sms-gateway/models/models.go (3)
SoftDeletableModel(12-15)User(17-23)Device(32-43)internal/sms-gateway/modules/messages/models.go (1)
Migrate(136-141)internal/sms-gateway/modules/settings/models.go (1)
Migrate(19-24)
internal/sms-gateway/handlers/messages/params.go (1)
internal/sms-gateway/modules/messages/repository_filter.go (3)
SelectFilter(16-23)SelectOptions(51-62)Order(7-7)
internal/sms-gateway/modules/db/health.go (1)
internal/sms-gateway/modules/health/types.go (1)
Provider(69-75)
internal/sms-gateway/modules/health/module.go (1)
internal/sms-gateway/modules/health/types.go (1)
Provider(69-75)
internal/sms-gateway/modules/webhooks/service.go (1)
internal/sms-gateway/modules/webhooks/errors.go (1)
ErrInvalidEvent(9-9)
internal/sms-gateway/modules/health/health.go (1)
internal/sms-gateway/modules/health/types.go (6)
Checks(67-67)CheckDetail(54-64)Status(7-7)StatusPass(11-11)StatusWarn(12-12)Provider(69-75)
internal/sms-gateway/app.go (2)
internal/sms-gateway/handlers/module.go (1)
Module(15-39)internal/sms-gateway/modules/devices/module.go (1)
Module(16-32)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
- GitHub Check: E2E
- GitHub Check: Benchmark
- GitHub Check: Analyze (go)
🔇 Additional comments (27)
internal/sms-gateway/handlers/messages/params.go (4)
4-4: LGTM: Import addition is appropriate.The
errorspackage import is correctly added for theerrors.New()usage on line 26.
32-56: LGTM: Type refactoring is correctly applied.The return type change from
messages.MessagesSelectFiltertomessages.SelectFilteraligns with the repository layer refactoring. The conversion logic remains intact and correct.
58-76: LGTM: Type refactoring correctly applied.The return type change to
messages.SelectOptionsis consistent with the repository layer refactoring. The logic correctly sets default values and applies limits.Optional note: The
min(p.Limit, maxLimit)on line 66 is technically redundant since the validation on line 20 already enforcesmax=100. However, this defensive approach is acceptable and doesn't cause issues.
78-87: No issues found. Code is correct.The
MessagesOrderLIFOconstant is properly defined as typeOrderininternal/sms-gateway/modules/messages/repository_filter.go(line 11:MessagesOrderLIFO Order = "lifo"), which is fully compatible with the refactored type signature inparams.go. The type change frommessages.MessagesOrdertomessages.Orderhas been consistently applied throughout the codebase.pkg/cache/options.go (1)
12-16: No breaking change—all call sites useapply()as a side-effect only.The verification shows that all five call sites in the codebase (
pkg/cache/redis.go:219,pkg/cache/redis.go:250,pkg/cache/memory.go:155,pkg/cache/redis.go:150,pkg/cache/memory.go:92) invokeapply()as standalone statements without chaining or consuming the return value. The removal of the return value causes no impact.Likely an incorrect or invalid review comment.
pkg/pubsub/options.go (1)
9-13: Non-fluentapplystill covers current usage.
options.applyno longer returns the receiver, but none of the constructors rely on chaining the result, so this keeps behavior intact and simplifies the API surface.pkg/pubsub/pubsub.go (1)
9-11: Good call addingErrInvalidConfig.Exposing a shared sentinel lets the constructors wrap config issues cleanly and makes upstream handling consistent.
internal/sms-gateway/modules/webhooks/repository_filter.go (1)
26-30: LGTM - Idiomatic pointer allocation.The change from composite literal to
new(selectFilter)is functionally identical and represents a minor style improvement for zero-value pointer allocation.internal/sms-gateway/modules/health/types.go (3)
20-24: LGTM - Public health check exposure.The addition of the
Checksfield with JSON tag exposes health check details in API responses, which aligns with standard health check endpoint patterns.
53-64: LGTM - Enhanced observability.The addition of
ObservedUnitandObservedValuefields follows standard health check patterns for exposing metrics.
69-75: LGTM - Simplified naming.Renaming
HealthProvidertoProvideris appropriate since the interface already resides in the health package. The method signatures remain unchanged.internal/sms-gateway/modules/health/module.go (2)
14-17: LGTM - Appropriate visibility restriction.Changing
NewHealthtonewHealth(unexported) is correct for fx-based DI where the constructor is only used within module wiring.
24-30: LGTM - Consistent with interface rename.Updated
fx.Asannotation correctly reflects theHealthProvider→Providerinterface rename.internal/sms-gateway/modules/db/health.go (1)
60-60: LGTM - Updated interface assertion.The compile-time assertion correctly reflects the
HealthProvider→Providerinterface rename.internal/sms-gateway/modules/health/service.go (3)
10-22: LGTM - Consistent type updates.Field types correctly updated from
HealthProvidertoProviderto reflect the interface rename.
32-35: LGTM - Improved formatting and type consistency.The multi-line parameter formatting with trailing comma follows Go conventions, and the
Providertype update aligns with the interface rename.
71-87: LGTM - Consistent closure type updates.All probe closures correctly updated to use the
Providertype while preserving the logic.internal/sms-gateway/modules/webhooks/module.go (2)
9-20: LGTM - Consistent module pattern.The conversion from exported variable to
Module()function aligns with the refactoring pattern applied consistently across other modules in this PR. Based on learnings.
22-25: LGTM - Appropriate linter directive.The
gochecknoinitssuppression is correctly applied with a clear explanation, as theinit()function is required for framework-specific migration registration.internal/sms-gateway/modules/webhooks/errors.go (2)
8-10: LGTM - Sentinel error pattern.The addition of
ErrInvalidEventfollows Go's sentinel error pattern, allowing callers to useerrors.Is()for error checking.
34-36: LGTM - Improved error checking.Using
errors.Asinstead of type assertion correctly handles wrapped errors and is more robust.internal/sms-gateway/modules/webhooks/repository.go (2)
12-16: LGTM - Standard DI constructor.The
NewRepositoryconstructor follows standard dependency injection patterns and aligns with the module wiring updates.
33-35: LGTM - Idiomatic nil pointer for type information.Using
(*Webhook)(nil)instead of&Webhook{}is a more idiomatic way to provide type information to GORM's Delete operation, making it clear that only the type is needed, not an actual instance.internal/sms-gateway/app.go (1)
36-62: Module factory conversion looks solid.Returning an
fx.Optionkeeps the consumer API consistent while giving us an immutable module wiring; the call sites invokingModule()now read clearly. Nice cleanup.internal/sms-gateway/modules/webhooks/service.go (1)
92-100: Constructor reuse keeps Replace concise.Switching to
newWebhook(...)centralizes the persistence shape and the move to the sharedErrInvalidEventkeeps validation errors uniform with the rest of the package.internal/sms-gateway/modules/webhooks/models.go (1)
38-41: Migration error wrapping appreciated.Using
new(Webhook)with wrapped errors aligns this migration with the other modules and should make diagnosing schema issues noticeably easier.internal/sms-gateway/modules/health/health.go (1)
22-51: Please double-check the tighter thresholds.Dropping the memory budget from roughly 1 GiB to 128 MiB and the goroutine cap to 100 is a big jump; if current workloads already hover above those numbers we’ll end up reporting WARN continuously. Can we confirm recent production metrics or make these probes configurable before shipping?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 1
🧹 Nitpick comments (4)
internal/sms-gateway/modules/db/health.go (1)
17-23: Optional: Remove redundant initialization.The explicit initialization of
failedPings: atomic.Int64{}is redundant sinceatomic.Int64is a struct type that will be zero-initialized automatically. You can simplify this to:func newHealth(db *sql.DB) *health { return &health{ db: db, - - failedPings: atomic.Int64{}, } }internal/sms-gateway/handlers/mobile.go (1)
69-69: Optional: Remove redundant nil assignment.In Go, struct fields are zero-initialized, so explicitly setting
Device: nilis unnecessary.- Device: nil, + Device: nil, // or simply omit if you prefer implicit zero-valueOr omit it entirely if the struct field should remain at its zero value.
internal/sms-gateway/modules/sse/service.go (2)
46-46: Redundant explicit initialization.The zero value of
sync.RWMutexis valid and ready to use in Go. The explicit initialization here is redundant, though harmless.Apply this diff if you prefer the more idiomatic approach:
- mu: sync.RWMutex{}, + mu: sync.RWMutex{}, // or simply omit this lineAlternatively, you can simply omit the line entirely since Go's zero value initialization handles it.
157-163: Simplify ticker handling pattern.The anonymous function pattern for conditional ticker handling is overly complex and creates a new channel on each iteration when the ticker is disabled. A simpler and more idiomatic approach is to use a nil channel, which blocks forever in a select statement.
Apply this diff to simplify the ticker handling:
+ var tickerChan <-chan time.Time + if ticker != nil { + tickerChan = ticker.C + } + for { select { case event := <-conn.channel: s.metrics.ObserveEventDeliveryLatency(func() { if err := s.writeToStream(w, fmt.Sprintf("event: %s\ndata: %s", event.name, utils.UnsafeString(event.data))); err != nil { s.logger.Warn("Failed to write event data", zap.String("device_id", deviceID), zap.String("connection_id", conn.id), zap.Error(err)) return } }) - // Conditionally handle ticker events - case <-func() <-chan time.Time { - if ticker != nil { - return ticker.C - } - // Return nil channel that never fires when disabled - return make(chan time.Time) - }(): + case <-tickerChan: if err := s.writeToStream(w, ":keepalive"); err != nil {When
tickerChanis nil, that case will never be selected by the select statement, which is the desired behavior for disabled keepalives.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (25)
internal/config/config.go(3 hunks)internal/config/module.go(1 hunks)internal/sms-gateway/app.go(2 hunks)internal/sms-gateway/handlers/mobile.go(8 hunks)internal/sms-gateway/handlers/module.go(1 hunks)internal/sms-gateway/handlers/upstream.go(3 hunks)internal/sms-gateway/models/migration.go(2 hunks)internal/sms-gateway/models/models.go(2 hunks)internal/sms-gateway/models/module.go(1 hunks)internal/sms-gateway/modules/cleaner/module.go(1 hunks)internal/sms-gateway/modules/cleaner/service.go(2 hunks)internal/sms-gateway/modules/db/health.go(3 hunks)internal/sms-gateway/modules/db/module.go(2 hunks)internal/sms-gateway/modules/devices/module.go(1 hunks)internal/sms-gateway/modules/devices/repository.go(3 hunks)internal/sms-gateway/modules/devices/repository_filter.go(1 hunks)internal/sms-gateway/modules/devices/service.go(6 hunks)internal/sms-gateway/modules/messages/service.go(11 hunks)internal/sms-gateway/modules/metrics/handler.go(1 hunks)internal/sms-gateway/modules/metrics/module.go(1 hunks)internal/sms-gateway/modules/sse/config.go(2 hunks)internal/sms-gateway/modules/sse/errors.go(1 hunks)internal/sms-gateway/modules/sse/metrics.go(3 hunks)internal/sms-gateway/modules/sse/module.go(1 hunks)internal/sms-gateway/modules/sse/service.go(8 hunks)
✅ Files skipped from review due to trivial changes (3)
- internal/sms-gateway/modules/sse/metrics.go
- internal/sms-gateway/models/module.go
- internal/sms-gateway/modules/devices/repository_filter.go
🚧 Files skipped from review as they are similar to previous changes (1)
- internal/sms-gateway/app.go
🧰 Additional context used
🧠 Learnings (3)
📚 Learning: 2025-09-10T23:45:45.502Z
Learnt from: capcom6
Repo: android-sms-gateway/server PR: 178
File: internal/sms-gateway/online/module.go:14-16
Timestamp: 2025-09-10T23:45:45.502Z
Learning: fx.Decorate in uber-go/fx creates module-scoped decorated dependencies, not global modifications. Using fx.Decorate(func(log *zap.Logger) *zap.Logger { return log.Named("module_name") }) is a correct pattern for creating module-specific loggers that are scoped only to that module's dependency graph.
Applied to files:
internal/sms-gateway/handlers/module.gointernal/sms-gateway/modules/cleaner/module.gointernal/sms-gateway/modules/devices/module.gointernal/sms-gateway/modules/metrics/module.go
📚 Learning: 2025-07-22T00:34:31.896Z
Learnt from: capcom6
Repo: android-sms-gateway/server PR: 154
File: internal/sms-gateway/modules/sse/service.go:89-92
Timestamp: 2025-07-22T00:34:31.896Z
Learning: In Go SSE implementations, when cleaning up connections with buffered channels, avoid closing the data channels directly as this can cause panics if concurrent goroutines attempt to write to them. Instead, use a separate close signal channel to notify writers that the connection should be terminated, allowing the data channels to be garbage collected naturally when the connection is removed.
Applied to files:
internal/sms-gateway/modules/sse/errors.gointernal/sms-gateway/modules/sse/service.go
📚 Learning: 2025-07-22T00:31:28.796Z
Learnt from: capcom6
Repo: android-sms-gateway/server PR: 154
File: internal/sms-gateway/modules/sse/service.go:67-79
Timestamp: 2025-07-22T00:31:28.796Z
Learning: In the SSE service Send method (internal/sms-gateway/modules/sse/service.go), blocking on full channels is intentional and acceptable. Timeout control is handled at the calling code level through context, not within the Send method itself. The user prefers this approach over adding a default case that would drop messages when channels are full.
Applied to files:
internal/sms-gateway/modules/sse/service.go
🧬 Code graph analysis (15)
internal/sms-gateway/modules/sse/module.go (2)
internal/sms-gateway/app.go (1)
Module(36-62)internal/sms-gateway/modules/sse/service.go (2)
NewService(42-52)Service(21-29)
internal/sms-gateway/modules/sse/config.go (1)
internal/config/config.go (1)
Config(10-20)
internal/sms-gateway/handlers/module.go (1)
internal/sms-gateway/app.go (1)
Module(36-62)
internal/sms-gateway/modules/cleaner/module.go (1)
internal/sms-gateway/app.go (1)
Module(36-62)
internal/sms-gateway/modules/messages/service.go (6)
internal/sms-gateway/modules/messages/repository_filter.go (3)
Order(7-7)SelectFilter(16-23)SelectOptions(51-62)internal/sms-gateway/modules/devices/repository_filter.go (2)
SelectFilter(9-9)WithUserID(23-27)internal/sms-gateway/modules/webhooks/repository_filter.go (4)
SelectFilter(5-5)WithExtID(7-11)WithDeviceID(41-46)WithUserID(13-17)internal/sms-gateway/modules/messages/models.go (3)
MessageState(129-134)ProcessingState(12-12)MessageRecipient(111-117)internal/sms-gateway/modules/messages/repository.go (1)
ErrMessageNotFound(18-18)internal/sms-gateway/modules/messages/errors.go (1)
ValidationError(9-9)
internal/sms-gateway/models/migration.go (1)
internal/sms-gateway/models/models.go (2)
User(18-24)Device(34-45)
internal/sms-gateway/modules/db/health.go (1)
internal/sms-gateway/modules/health/types.go (2)
Checks(67-67)Provider(69-75)
internal/sms-gateway/modules/devices/repository.go (1)
internal/sms-gateway/models/models.go (1)
Device(34-45)
internal/config/module.go (9)
internal/sms-gateway/app.go (1)
Module(36-62)internal/sms-gateway/modules/sse/config.go (4)
Option(5-5)Config(7-9)NewConfig(19-27)WithKeepAlivePeriod(33-41)internal/config/config.go (12)
Config(10-20)Default(89-127)HTTP(27-33)Database(44-56)Gateway(22-25)Tasks(64-66)API(35-38)OpenAPI(40-42)Messages(76-79)SSE(72-74)Cache(81-83)PubSub(85-87)internal/sms-gateway/modules/auth/service.go (1)
Config(22-25)internal/sms-gateway/modules/push/service.go (1)
Config(23-30)internal/sms-gateway/modules/messages/config.go (1)
Config(5-8)internal/sms-gateway/pubsub/config.go (1)
Config(4-7)internal/sms-gateway/handlers/config.go (1)
Config(3-11)internal/sms-gateway/modules/devices/config.go (1)
Config(5-7)
internal/sms-gateway/handlers/mobile.go (1)
internal/sms-gateway/models/models.go (2)
Device(34-45)User(18-24)
internal/sms-gateway/modules/devices/service.go (5)
internal/sms-gateway/modules/devices/config.go (1)
Config(5-7)internal/sms-gateway/models/models.go (1)
Device(34-45)internal/sms-gateway/modules/db/module.go (1)
IDGen(14-14)internal/sms-gateway/modules/devices/repository_filter.go (4)
SelectFilter(9-9)WithUserID(23-27)WithID(11-15)ActiveWithin(29-33)internal/sms-gateway/modules/devices/repository.go (1)
ErrNotFound(13-13)
internal/sms-gateway/modules/sse/service.go (2)
internal/sms-gateway/modules/sse/errors.go (1)
ErrNoConnection(6-6)internal/sms-gateway/modules/sse/metrics.go (1)
ErrorTypeWriteFailure(21-21)
internal/config/config.go (3)
internal/sms-gateway/modules/messages/config.go (1)
Config(5-8)internal/sms-gateway/pubsub/config.go (1)
Config(4-7)internal/sms-gateway/cache/config.go (1)
Config(4-6)
internal/sms-gateway/modules/devices/module.go (3)
internal/sms-gateway/app.go (1)
Module(36-62)internal/sms-gateway/modules/sse/module.go (1)
Module(10-32)internal/sms-gateway/modules/messages/module.go (1)
Module(11-35)
internal/sms-gateway/modules/metrics/module.go (7)
internal/config/module.go (1)
Module(23-143)internal/sms-gateway/app.go (1)
Module(36-62)internal/sms-gateway/handlers/module.go (1)
Module(15-41)internal/sms-gateway/modules/cleaner/module.go (1)
Module(28-38)internal/sms-gateway/modules/db/module.go (1)
Module(16-26)internal/sms-gateway/modules/devices/module.go (1)
Module(16-35)internal/sms-gateway/modules/sse/module.go (1)
Module(10-32)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
- GitHub Check: E2E
- GitHub Check: Benchmark
- GitHub Check: Analyze (go)
🔇 Additional comments (39)
internal/sms-gateway/handlers/upstream.go (2)
68-68: Validation error fix looks good!This change correctly addresses the previous critical issue by returning HTTP 400 Bad Request for validation errors instead of 502 Bad Gateway.
97-103: Good refactoring with named constants.Extracting the rate limiting values into named constants improves code readability and makes future adjustments easier.
internal/sms-gateway/modules/db/health.go (2)
31-33: LGTM! Probe implementations are correct.The use of unnamed context parameters (
_) for unused contexts is idiomatic Go. Returningnil, nilfor probes that don't report checks is reasonable, and thenolint:nilnilcomments appropriately document this intentional pattern for empty results.Also applies to: 58-60
62-62: LGTM! Interface assertion updated correctly.The change from
healthmod.HealthProvidertohealthmod.Providercorrectly reflects the interface rename in the health module.internal/sms-gateway/handlers/mobile.go (6)
99-101: Body parsing error handling fixed correctly.The previous issue where
fiber.StatusBadGateway(502) was returned for body parsing errors has been resolved. The handler now correctly returnsfiber.StatusBadRequest(400) for client-side validation failures.
150-168: LGTM: Improved error handling.The function correctly returns
400 Bad Requestfor body parsing errors and implements proper error logging with generic client-facing error messages, which is a good security practice.
180-192: LGTM: Consistent error handling.The error handling follows the same pattern as other handlers, logging detailed errors while returning generic messages to clients.
207-221: Previous issue resolved.The body parsing error handling now correctly returns
fiber.StatusBadRequest(400) instead offiber.StatusBadGateway(502), addressing the previous review feedback.
236-243: LGTM: Validator function improvements.The validator function correctly uses an underscore for the unused context parameter and provides better error context through error wrapping.
124-124: No issues found; dereferencing is correct.The
RegisterDevicemethod signature expectsuser models.User(a value type), not a pointer. The code correctly dereferences*userto match this parameter type.internal/sms-gateway/modules/devices/service.go (1)
9-10: math/rand/v2 is supported.The module's go directive is set to
go 1.24.1, which exceeds the minimum Go 1.22 requirement formath/rand/v2. No toolchain compatibility issues exist.internal/sms-gateway/models/migration.go (1)
14-17: Nice addition—migration errors now have context.Wrapping the
AutoMigratefailure with module-specific context tightens observability without altering behavior. 👍internal/sms-gateway/modules/sse/errors.go (1)
5-7: Good call introducing a sentinel.
ErrNoConnectiongives callers a stable error to match, which will simplify handling across the SSE surface.internal/sms-gateway/modules/sse/module.go (1)
10-31: Module() conversion looks solid.Function form keeps the same wiring while aligning with the new fx.Module pattern; lifecycle cleanup remains intact.
internal/sms-gateway/modules/metrics/module.go (1)
9-18: Metrics module refactor matches the new pattern.Switching to
Module()keeps DI wiring unchanged and updates the handler constructor naming to the newnewHTTPHandler.internal/sms-gateway/modules/devices/module.go (1)
16-33: Devices module wiring preserved.The
Module()helper mirrors existing behavior, and the explicitfx.Outkeeps the FxResult intent clear.internal/config/config.go (1)
89-126: Centralizing defaults pays off.The new
Default()helper gives a single source of truth for baseline config before env/YAML overrides, which modernizes the initialization flow.internal/sms-gateway/modules/cleaner/module.go (1)
28-37: Cleaner module factory pattern confirmedSwitching to a Module() factory keeps the scoped zap logger decoration and NewFx wiring intact, so downstream callers just need to invoke the factory. Based on learnings.
internal/sms-gateway/handlers/module.go (1)
15-40: Handlers wiring now exported as factoryThe new Module() factory keeps the handlers logger decoration and grouped provides exactly as before, matching the updated Fx integration style. Based on learnings.
internal/sms-gateway/modules/metrics/handler.go (1)
8-19: HTTPHandler rename improves export clarityThe all-caps acronym keeps naming idiomatic while preserving Register semantics; constructor stays minimal and focused.
internal/sms-gateway/modules/devices/repository.go (1)
39-104: Nil model usage avoids per-call allocationsUsing typed nil pointers with Model/Delete keeps GORM targeting the devices table while side-stepping needless struct allocations; Exists still short-circuits cleanly.
internal/config/module.go (1)
22-142: App config module factory keeps providers intactThe new Module() factory still loads defaults, scopes Config privately, and exposes the same downstream provider set while documenting constants like the SSE write timeout.
internal/sms-gateway/modules/sse/service.go (5)
17-19: LGTM!Good practice extracting the buffer size into a named constant for improved maintainability.
62-62: LGTM!The error wrapping with device ID context is a good improvement that aligns with Go error handling best practices and enables better error tracing.
Also applies to: 98-98
79-83: Also applies to: 85-89, 227-231
127-131: LGTM!Good refactoring that separates HTTP setup concerns from streaming logic, improving testability and code organization.
180-190: LGTM!The error wrapping with descriptive messages improves error tracing and makes debugging easier.
internal/sms-gateway/modules/messages/service.go (12)
18-18: LGTM: Adding functional programming utilities.The
lolibrary is well-established for functional transformations and is used appropriately throughout this file.
77-88: LGTM: Method signature updated consistently.The signature change from
MessagesOrdertoOrderaligns with the broader type refactoring, and the default value handling is appropriate.
104-114: LGTM: Clean functional transformation.The use of
lo.MapToSliceappropriately converts the states map to a slice ofMessageStatestructs. SettingID: 0is correct for auto-generated GORM IDs.
117-123: LGTM: Clear error handling with appropriate failure semantics.The renamed error variables (
updErr,cacheErr) improve readability, and treating cache errors as non-critical (warn-only) is appropriate for this operation.
130-143: LGTM: Signature refactored consistently.The method signature change from
MessagesSelectFilter/MessagesSelectOptionstoSelectFilter/SelectOptionsaligns with the repository layer refactoring.
145-178: LGTM: Improved error handling and caching strategy.The caching strategy correctly handles "not found" cases by storing nil entries (lines 151-153), preventing repeated database lookups. The consistent error handling with
cacheErris appropriate.
255-261: LGTM: Improved log readability.The multi-line format for structured logging improves readability when multiple context fields are present.
270-275: LGTM: Enhanced error wrapping.The error wrapping with
%wprovides better error context and maintains the error chain forerrors.Ischecks.
288-292: LGTM: Cleaner functional implementation.The refactor to
lo.Mapwith thenewMessageRecipienthelper is more concise and maintains the same logic.
312-317: LGTM: Consistent helper usage.The use of
newMessageRecipienthelper maintains consistency with the refactoredrecipientsToModelmethod.
352-361: LGTM: Custom error type for validation.The use of
ValidationErroras a custom string type for phone validation errors is a valid Go pattern, assumingValidationErrorimplements theError()method (standard for typed error patterns).
91-94: The*new(SelectFilter)pattern is valid but unconventional.The builder methods (
WithExtID,WithDeviceID) onSelectFilteruse pointer receivers and return*SelectFilter. The code creates a pointer withnew(), chains methods on it, then dereferences with*to pass a value toGet().This works correctly but sacrifices readability. More idiomatic approaches:
- Assign to a variable:
sf := new(SelectFilter); sf.WithExtID(...).WithDeviceID(...)- Or redesign builders to return values instead of pointers for fluent chaining
The current pattern is not incorrect, but consider simplifying for maintainability.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 0
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
internal/sms-gateway/modules/sse/service.go (1)
215-240: Critical: Potential panic from double-close of closeSignal channel.The
closeSignalchannel can be closed twice:
- During service shutdown,
Close()(line 113) closes allcloseSignalchannels- This causes
handleStreamto return (line 171), triggering the deferredremoveConnection(line 135)removeConnectionthen attempts to close the samecloseSignalagain (line 222), causing a panicClosing an already-closed channel panics in Go.
Apply this diff to fix by removing the close from
removeConnection(cleanup happens inClose()or naturally when the connection is removed):func (s *Service) removeConnection(deviceID, connID string) { s.mu.Lock() defer s.mu.Unlock() if connections, exists := s.connections[deviceID]; exists { for i, conn := range connections { if conn.id == connID { - close(conn.closeSignal) s.connections[deviceID] = append(connections[:i], connections[i+1:]...) s.logger.Info( "Removing SSE connection", zap.String("device_id", deviceID), zap.String("connection_id", connID), ) break } } // Decrement active connections metric s.metrics.DecrementActiveConnections() if len(s.connections[deviceID]) == 0 { delete(s.connections, deviceID) } } }Note: The
closeSignalchannel will be garbage collected when the connection is removed from the map. Based on learnings, this aligns with the pattern of not closing channels that concurrent goroutines might write to.
🧹 Nitpick comments (1)
internal/sms-gateway/modules/sse/service.go (1)
46-46: Consider removing redundant initialization.The zero value of
sync.RWMutexis already properly initialized in Go. The explicit initialization here is unnecessary.Apply this diff to simplify:
- mu: sync.RWMutex{}, connections: make(map[string][]*sseConnection),
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (1)
internal/sms-gateway/modules/sse/service.go(8 hunks)
🧰 Additional context used
🧠 Learnings (2)
📚 Learning: 2025-07-22T00:31:28.796Z
Learnt from: capcom6
Repo: android-sms-gateway/server PR: 154
File: internal/sms-gateway/modules/sse/service.go:67-79
Timestamp: 2025-07-22T00:31:28.796Z
Learning: In the SSE service Send method (internal/sms-gateway/modules/sse/service.go), blocking on full channels is intentional and acceptable. Timeout control is handled at the calling code level through context, not within the Send method itself. The user prefers this approach over adding a default case that would drop messages when channels are full.
Applied to files:
internal/sms-gateway/modules/sse/service.go
📚 Learning: 2025-07-22T00:34:31.896Z
Learnt from: capcom6
Repo: android-sms-gateway/server PR: 154
File: internal/sms-gateway/modules/sse/service.go:89-92
Timestamp: 2025-07-22T00:34:31.896Z
Learning: In Go SSE implementations, when cleaning up connections with buffered channels, avoid closing the data channels directly as this can cause panics if concurrent goroutines attempt to write to them. Instead, use a separate close signal channel to notify writers that the connection should be terminated, allowing the data channels to be garbage collected naturally when the connection is removed.
Applied to files:
internal/sms-gateway/modules/sse/service.go
🧬 Code graph analysis (1)
internal/sms-gateway/modules/sse/service.go (2)
internal/sms-gateway/modules/sse/errors.go (1)
ErrNoConnection(6-6)internal/sms-gateway/modules/sse/metrics.go (1)
ErrorTypeWriteFailure(21-21)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
- GitHub Check: E2E
- GitHub Check: Benchmark
- GitHub Check: Analyze (go)
🔇 Additional comments (6)
internal/sms-gateway/modules/sse/service.go (6)
17-19: LGTM! Good practice to extract magic number into a named constant.This improves maintainability and makes the buffer size configuration more explicit.
54-105: LGTM! Improved error handling and logging.The changes to wrap errors with
ErrNoConnectionusing%wenable proper error inspection witherrors.Is(). The multiline structured logging is also more readable.
120-131: LGTM! Good refactoring for separation of concerns.Extracting the streaming logic into
handleStreamimproves code organization and testability.
133-174: LGTM! Well-structured streaming handler.The connection lifecycle management with defer cleanup, conditional keep-alive ticker, and proper metric tracking after successful writes are all implemented correctly. The nil channel pattern for conditional ticker selection is elegant.
176-187: LGTM! Proper error handling and wrapping.The descriptive error messages with
%wwrapping and metric tracking on failures are well implemented.
189-213: LGTM! Consistent use of buffer size constant.The connection registration logic is correct and properly uses the
eventsBufferSizeconstant.
c3cf1a4 to
a6dda30
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 8
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (7)
pkg/cache/memory_profile_test.go (2)
123-143: Fix therangeloops to restore compilation.
rangeover anint(e.g.,for i := range numCaches) does not compile in Go;rangeonly works on arrays, slices, maps, strings, or channels. Revert to the classic counting loop so these tests build.- for i := range numCaches { + for i := 0; i < numCaches; i++ {
227-247: Restore the counting loops here, too.Same issue:
rangeover theintconstantsnumCaches/10fails to compile. Switch back to the standardfor i := 0; i < ...; i++form.- for i := range numCaches { + for i := 0; i < numCaches; i++ { @@ - for j := range 10 { + for j := 0; j < 10; j++ {pkg/cache/memory_bench_test.go (3)
283-303: Counting loops must remain explicit.
for i := range bm.operationsPerGoroutine * bm.goroutinesis invalid becauserangecannot iterate anint. Please revert to the classic indexed loop (same applies to other addedrangeloops in this file).- for i := range bm.operationsPerGoroutine * bm.goroutines { + for i := 0; i < bm.operationsPerGoroutine*bm.goroutines; i++ {
389-399:range sizealso breaks compilation.Here
sizeis anint, so this loop no longer compiles. Restore the indexed form.- for j := range size { + for j := 0; j < size; j++ {
451-458: Revert the cold-key prefill loop.Same compile-time problem:
numKeysis anint, sorangecannot be used directly.- for i := range numKeys { + for i := 0; i < numKeys; i++ {internal/sms-gateway/modules/health/types.go (1)
31-51: Replace undefined max helper in Status computation.
maxisn’t defined anywhere in this package, so this change introduces a compile error. Use explicit comparisons to bump the level instead.- level := levelPass - for _, detail := range c.Checks { - switch detail.Status { - case StatusPass: - case StatusFail: - level = max(level, levelFail) - case StatusWarn: - level = max(level, levelWarn) - } - } + level := levelPass + for _, detail := range c.Checks { + switch detail.Status { + case StatusFail: + level = levelFail + case StatusWarn: + if level < levelWarn { + level = levelWarn + } + } + }internal/sms-gateway/handlers/mobile.go (1)
110-124: Add nil check afteruserauth.GetUser(c)to ensure user safety.The
GetUserfunction can return nil, but line 111 dereferences it immediately without checking:login = user.ID. Even thoughHasUserreturns true, there's no guaranteeGetUserwon't return nil. Add a nil check in the if-branch:if userauth.HasUser(c) { user = userauth.GetUser(c) if user == nil { return fmt.Errorf("user not found") } login = user.ID }
♻️ Duplicate comments (4)
.github/workflows/pr.yml (2)
92-92: Pin the S3 upload action to a specific version.Using
@masterintroduces supply-chain risk. Pin to a stable release tag or commit SHA for reproducibility and security.- uses: capcom6/upload-s3-action@master + uses: capcom6/upload-s3-action@v1.0.0(Replace with the appropriate stable version.)
111-118: Critical: GoReleaser does not produce per-arch image tags.The
docker tagcommands at lines 111-112 referencelatest-arm64andlatest-amd64tags that GoReleaser does not create. The.goreleaser.yamlconfiguration usesdockers_v2with multi-platform builds, which produces a single multi-arch manifest with tags likelatest, not separate per-architecture tags. This workflow will fail when these docker tag commands execute.You need to either:
- Update
.goreleaser.yamlto produce separate per-arch images with appropriate tags, or- Modify this workflow to work with GoReleaser's multi-arch manifest approach (e.g., pull and re-tag the multi-arch images, or use buildx to inspect and extract platform-specific digests)
For option 1, you could add separate docker build entries for each platform in
.goreleaser.yaml. For option 2, you'd need to adjust the tagging strategy to work with manifests.internal/sms-gateway/modules/messages/repository.go (1)
180-194: Critical issue from previous review remains unfixed: Double%wwill panic.Line 185 still uses
fmt.Errorf("%w: %w", ErrLockFailed, err). Thefmt.Errorffunction only supports one%wverb; using two will cause a panic. This was flagged in the previous review but remains unaddressed.Apply this fix:
err := lockRow.Scan(&hasLock) if err != nil { - return fmt.Errorf("%w: %w", ErrLockFailed, err) + return fmt.Errorf("%w: %v", ErrLockFailed, err) }internal/sms-gateway/handlers/mobile.go (1)
275-276: Past issue remains unaddressed: Ignored error lacks documentation.The previous review requested either error handling or a comment explaining why it's safe to ignore the error from
nanoid.Standard(idGenSize). While a constant was added, there's still no comment documenting why the error can be safely ignored with this constant size.Add a brief comment:
const idGenSize = 21 + // nanoid.Standard with size 21 never returns an error idGen, _ := nanoid.Standard(idGenSize)
🧹 Nitpick comments (9)
internal/sms-gateway/pubsub/pubsub.go (2)
32-32: Clarify the tuple assignment syntax.The syntax
pubSub, err = pubsub.NewMemory(opts...), nilis valid but potentially confusing. SinceNewMemoryreturns only*MemoryPubSubwithout an error, consider making the intent clearer.Apply this diff for improved clarity:
switch u.Scheme { case "memory": - pubSub, err = pubsub.NewMemory(opts...), nil + pubSub = pubsub.NewMemory(opts...) + err = nil case "redis":
40-40: Define a static error for invalid scheme.The linter correctly flags this dynamic error. Following Go best practices and the error-wrapping pattern used elsewhere in this PR, define a static sentinel error.
Based on the linting hint, apply this approach:
Add a static error at the package level:
var ErrInvalidScheme = errors.New("invalid scheme")Then wrap it with context:
default: - return nil, fmt.Errorf("invalid scheme: %s", u.Scheme) + return nil, fmt.Errorf("%w: %s", ErrInvalidScheme, u.Scheme) }pkg/pubsub/memory.go (1)
33-34: Remove redundant zero-value initialization.The explicit initialization of
sync.WaitGroup{}andsync.RWMutex{}is redundant since Go automatically zero-initializes struct fields.Apply this diff to simplify:
return &MemoryPubSub{ bufferSize: o.bufferSize, - wg: sync.WaitGroup{}, - mu: sync.RWMutex{}, topics: make(map[string]map[string]subscriber), closeCh: make(chan struct{}), }internal/sms-gateway/modules/sse/metrics.go (1)
55-61: Consider adding event_type label to eventDeliveryLatency.While the histogram is correctly implemented, adding an
event_typelabel would provide more granular observability, allowing you to track latency per event type. This could be valuable for identifying performance issues with specific event types.If desired, apply this diff:
- eventDeliveryLatency: promauto.NewHistogramVec(prometheus.HistogramOpts{ - Namespace: "sms", - Subsystem: "sse", - Name: MetricEventLatency, - Help: "Event delivery latency in seconds", - Buckets: []float64{0.005, 0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1}, - }, []string{}), + eventDeliveryLatency: promauto.NewHistogramVec(prometheus.HistogramOpts{ + Namespace: "sms", + Subsystem: "sse", + Name: MetricEventLatency, + Help: "Event delivery latency in seconds, labeled by event type", + Buckets: []float64{0.005, 0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1}, + }, []string{LabelEventType}),And update the
ObserveEventDeliveryLatencymethod to accept an event type:func (m *metrics) ObserveEventDeliveryLatency(eventType string, f func()) { timer := prometheus.NewTimer(m.eventDeliveryLatency.WithLabelValues(eventType)) f() timer.ObserveDuration() }internal/sms-gateway/modules/devices/module.go (1)
31-31: Optional: Redundant Out initialization.The explicit
Out: fx.Out{}initialization is redundant sincefx.Outis an embedded field and will be zero-initialized automatically. However, it's harmless and may aid clarity.Consider removing for brevity:
return FxResult{ Service: svc, AsCleaner: svc, - Out: fx.Out{}, }internal/sms-gateway/handlers/logs/3rdparty.go (1)
24-30: Keep log names scoped to the logs controller.The previous constructor labeled this logger with
.Named("logs"), which made these entries easy to identify when triaging. Dropping that name causes the logs controller to emit under the root logger, reducing signal. Please keep the scoped name.Apply this diff to restore the scope:
Handler: base.Handler{ - Logger: params.Logger, + Logger: params.Logger.Named("logs"), Validator: params.Validator, },internal/sms-gateway/handlers/settings/3rdparty.go (2)
85-85: Consider restoring map pre-sizing for minor performance gain.The previous
make(map[string]any, 8)pre-allocated capacity for the settings map. While not critical, restoring the capacity hint can avoid 1-2 reallocations when parsing the request body.- settings := make(map[string]any) + settings := make(map[string]any, 8)
119-119: Consider restoring map pre-sizing for minor performance gain.Same as the
puthandler above, restoringmake(map[string]any, 8)can avoid reallocations.- settings := make(map[string]any) + settings := make(map[string]any, 8)internal/sms-gateway/modules/push/upstream/client.go (1)
86-91: Consider differentiating client vs. server errors.The current threshold treats all 400+ status codes as
ErrInvalidResponse. Consider whether 4xx (client errors) and 5xx (server errors) should be distinguished for retry logic or monitoring purposes.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
⛔ Files ignored due to path filters (1)
go.sumis excluded by!**/*.sum
📒 Files selected for processing (107)
.github/workflows/docker-build.yml(0 hunks).github/workflows/docker-publish.yml(0 hunks).github/workflows/go.yml(3 hunks).github/workflows/pr.yml(1 hunks).github/workflows/release.yml(2 hunks).gitignore(1 hunks).golangci.yml(1 hunks).goreleaser.yaml(1 hunks)Dockerfile.goreleaser(1 hunks)Makefile(2 hunks)internal/config/config.go(3 hunks)internal/config/module.go(1 hunks)internal/sms-gateway/app.go(2 hunks)internal/sms-gateway/cache/errors.go(1 hunks)internal/sms-gateway/cache/factory.go(2 hunks)internal/sms-gateway/handlers/base/handler_test.go(5 hunks)internal/sms-gateway/handlers/converters/messages.go(1 hunks)internal/sms-gateway/handlers/devices/3rdparty.go(3 hunks)internal/sms-gateway/handlers/events/mobile.go(1 hunks)internal/sms-gateway/handlers/health.go(2 hunks)internal/sms-gateway/handlers/logs/3rdparty.go(2 hunks)internal/sms-gateway/handlers/messages/3rdparty.go(8 hunks)internal/sms-gateway/handlers/messages/mobile.go(2 hunks)internal/sms-gateway/handlers/messages/params.go(3 hunks)internal/sms-gateway/handlers/middlewares/userauth/userauth.go(2 hunks)internal/sms-gateway/handlers/mobile.go(8 hunks)internal/sms-gateway/handlers/module.go(1 hunks)internal/sms-gateway/handlers/root.go(1 hunks)internal/sms-gateway/handlers/settings/3rdparty.go(4 hunks)internal/sms-gateway/handlers/settings/mobile.go(2 hunks)internal/sms-gateway/handlers/upstream.go(3 hunks)internal/sms-gateway/handlers/webhooks/3rdparty.go(4 hunks)internal/sms-gateway/handlers/webhooks/mobile.go(2 hunks)internal/sms-gateway/models/migration.go(2 hunks)internal/sms-gateway/models/models.go(2 hunks)internal/sms-gateway/models/module.go(1 hunks)internal/sms-gateway/modules/auth/errors.go(1 hunks)internal/sms-gateway/modules/auth/module.go(1 hunks)internal/sms-gateway/modules/auth/repository.go(1 hunks)internal/sms-gateway/modules/auth/service.go(6 hunks)internal/sms-gateway/modules/auth/types.go(1 hunks)internal/sms-gateway/modules/cleaner/module.go(1 hunks)internal/sms-gateway/modules/cleaner/service.go(2 hunks)internal/sms-gateway/modules/db/health.go(3 hunks)internal/sms-gateway/modules/db/module.go(2 hunks)internal/sms-gateway/modules/devices/module.go(1 hunks)internal/sms-gateway/modules/devices/repository.go(3 hunks)internal/sms-gateway/modules/devices/repository_filter.go(1 hunks)internal/sms-gateway/modules/devices/service.go(6 hunks)internal/sms-gateway/modules/events/errors.go(1 hunks)internal/sms-gateway/modules/events/metrics.go(3 hunks)internal/sms-gateway/modules/events/module.go(1 hunks)internal/sms-gateway/modules/events/service.go(6 hunks)internal/sms-gateway/modules/events/types.go(2 hunks)internal/sms-gateway/modules/health/health.go(2 hunks)internal/sms-gateway/modules/health/module.go(2 hunks)internal/sms-gateway/modules/health/service.go(3 hunks)internal/sms-gateway/modules/health/types.go(3 hunks)internal/sms-gateway/modules/messages/cache.go(2 hunks)internal/sms-gateway/modules/messages/converters.go(1 hunks)internal/sms-gateway/modules/messages/errors.go(1 hunks)internal/sms-gateway/modules/messages/models.go(6 hunks)internal/sms-gateway/modules/messages/module.go(1 hunks)internal/sms-gateway/modules/messages/repository.go(5 hunks)internal/sms-gateway/modules/messages/repository_filter.go(2 hunks)internal/sms-gateway/modules/messages/service.go(11 hunks)internal/sms-gateway/modules/messages/service_test.go(0 hunks)internal/sms-gateway/modules/metrics/handler.go(1 hunks)internal/sms-gateway/modules/metrics/module.go(1 hunks)internal/sms-gateway/modules/push/client.go(1 hunks)internal/sms-gateway/modules/push/client/types.go(1 hunks)internal/sms-gateway/modules/push/fcm/client.go(4 hunks)internal/sms-gateway/modules/push/fcm/errors.go(1 hunks)internal/sms-gateway/modules/push/fcm/utils.go(1 hunks)internal/sms-gateway/modules/push/module.go(2 hunks)internal/sms-gateway/modules/push/service.go(11 hunks)internal/sms-gateway/modules/push/types.go(3 hunks)internal/sms-gateway/modules/push/upstream/client.go(5 hunks)internal/sms-gateway/modules/settings/models.go(1 hunks)internal/sms-gateway/modules/settings/module.go(1 hunks)internal/sms-gateway/modules/settings/repository.go(5 hunks)internal/sms-gateway/modules/settings/service.go(2 hunks)internal/sms-gateway/modules/settings/utils.go(4 hunks)internal/sms-gateway/modules/sse/config.go(2 hunks)internal/sms-gateway/modules/sse/errors.go(1 hunks)internal/sms-gateway/modules/sse/metrics.go(3 hunks)internal/sms-gateway/modules/sse/module.go(1 hunks)internal/sms-gateway/modules/sse/service.go(8 hunks)internal/sms-gateway/modules/webhooks/errors.go(3 hunks)internal/sms-gateway/modules/webhooks/models.go(1 hunks)internal/sms-gateway/modules/webhooks/module.go(1 hunks)internal/sms-gateway/modules/webhooks/repository.go(2 hunks)internal/sms-gateway/modules/webhooks/repository_filter.go(1 hunks)internal/sms-gateway/modules/webhooks/service.go(2 hunks)internal/sms-gateway/openapi/docs.go(5 hunks)internal/sms-gateway/pubsub/module.go(2 hunks)internal/sms-gateway/pubsub/pubsub.go(1 hunks)pkg/cache/errors.go(1 hunks)pkg/cache/memory.go(12 hunks)pkg/cache/memory_bench_test.go(19 hunks)pkg/cache/memory_concurrency_test.go(9 hunks)pkg/cache/memory_edge_test.go(8 hunks)pkg/cache/memory_profile_test.go(2 hunks)pkg/cache/memory_test.go(8 hunks)pkg/cache/options.go(1 hunks)pkg/cache/redis.go(15 hunks)pkg/pubsub/memory.go(8 hunks)
⛔ Files not processed due to max files limit (5)
- pkg/pubsub/options.go
- pkg/pubsub/pubsub.go
- pkg/pubsub/redis.go
- test/e2e/mobile_test.go
- test/e2e/utils_test.go
💤 Files with no reviewable changes (3)
- .github/workflows/docker-build.yml
- internal/sms-gateway/modules/messages/service_test.go
- .github/workflows/docker-publish.yml
🚧 Files skipped from review as they are similar to previous changes (33)
- internal/sms-gateway/models/module.go
- internal/sms-gateway/modules/events/types.go
- internal/sms-gateway/modules/sse/module.go
- internal/sms-gateway/handlers/upstream.go
- internal/sms-gateway/modules/sse/errors.go
- internal/sms-gateway/modules/sse/service.go
- internal/sms-gateway/handlers/events/mobile.go
- pkg/cache/memory_edge_test.go
- internal/sms-gateway/modules/devices/repository_filter.go
- internal/sms-gateway/modules/settings/service.go
- internal/sms-gateway/cache/errors.go
- internal/sms-gateway/modules/db/health.go
- internal/sms-gateway/modules/webhooks/errors.go
- internal/sms-gateway/handlers/messages/params.go
- internal/sms-gateway/app.go
- internal/sms-gateway/modules/messages/converters.go
- internal/sms-gateway/handlers/base/handler_test.go
- .golangci.yml
- .gitignore
- internal/sms-gateway/handlers/module.go
- internal/sms-gateway/modules/messages/repository_filter.go
- internal/sms-gateway/modules/db/module.go
- internal/sms-gateway/modules/events/errors.go
- internal/sms-gateway/modules/messages/module.go
- internal/sms-gateway/handlers/health.go
- internal/sms-gateway/modules/health/service.go
- internal/sms-gateway/handlers/messages/mobile.go
- internal/sms-gateway/modules/settings/repository.go
- internal/sms-gateway/modules/push/service.go
- internal/sms-gateway/modules/messages/models.go
- Dockerfile.goreleaser
- internal/sms-gateway/modules/sse/config.go
- internal/sms-gateway/modules/messages/errors.go
🧰 Additional context used
🧠 Learnings (6)
📚 Learning: 2025-09-10T23:45:45.502Z
Learnt from: capcom6
Repo: android-sms-gateway/server PR: 178
File: internal/sms-gateway/online/module.go:14-16
Timestamp: 2025-09-10T23:45:45.502Z
Learning: fx.Decorate in uber-go/fx creates module-scoped decorated dependencies, not global modifications. Using fx.Decorate(func(log *zap.Logger) *zap.Logger { return log.Named("module_name") }) is a correct pattern for creating module-specific loggers that are scoped only to that module's dependency graph.
Applied to files:
internal/sms-gateway/modules/auth/module.gointernal/sms-gateway/modules/metrics/module.gointernal/sms-gateway/modules/events/module.gointernal/sms-gateway/pubsub/module.gointernal/sms-gateway/modules/settings/module.gointernal/sms-gateway/modules/cleaner/module.gointernal/sms-gateway/modules/webhooks/module.gointernal/sms-gateway/modules/devices/module.go
📚 Learning: 2025-09-10T23:53:42.006Z
Learnt from: capcom6
Repo: android-sms-gateway/server PR: 178
File: pkg/cache/redis.go:63-75
Timestamp: 2025-09-10T23:53:42.006Z
Learning: In pkg/cache/redis.go, the Redis cache implementation uses Redis's native field-level TTL via HExpireAt, meaning expired fields are automatically removed by Redis itself. This differs from the memory implementation where the application tracks expiration times. As a result, operations like Drain that call HGetAll will only return non-expired items since expired fields are already removed by Redis.
Applied to files:
pkg/cache/options.gopkg/cache/memory.gopkg/cache/redis.go
📚 Learning: 2025-09-10T11:11:43.462Z
Learnt from: capcom6
Repo: android-sms-gateway/server PR: 178
File: pkg/cache/memory.go:59-74
Timestamp: 2025-09-10T11:11:43.462Z
Learning: In pkg/cache/memory.go, the cleanup method removes expired items first, then executes the provided callback function. This means methods like Drain that use cleanup don't need additional expiration filtering since expired items are already removed before the callback runs.
Applied to files:
pkg/cache/memory_concurrency_test.gopkg/cache/memory_test.gopkg/cache/memory_profile_test.gopkg/cache/memory.gopkg/cache/memory_bench_test.gopkg/cache/redis.go
📚 Learning: 2025-07-22T00:31:28.796Z
Learnt from: capcom6
Repo: android-sms-gateway/server PR: 154
File: internal/sms-gateway/modules/sse/service.go:67-79
Timestamp: 2025-07-22T00:31:28.796Z
Learning: In the SSE service Send method (internal/sms-gateway/modules/sse/service.go), blocking on full channels is intentional and acceptable. Timeout control is handled at the calling code level through context, not within the Send method itself. The user prefers this approach over adding a default case that would drop messages when channels are full.
Applied to files:
internal/sms-gateway/modules/events/service.go
📚 Learning: 2025-09-05T08:37:28.077Z
Learnt from: eznix86
Repo: android-sms-gateway/server PR: 172
File: deployments/kubernetes/sms-gateway-server/Chart.yaml:9-10
Timestamp: 2025-09-05T08:37:28.077Z
Learning: In the android-sms-gateway/server project, the Helm chart's appVersion in Chart.yaml is set to "latest" as a placeholder because CI automatically updates it with the actual version from git tags during the release process via the bump-chart-app-version.yaml workflow.
Applied to files:
.goreleaser.yaml
📚 Learning: 2025-09-10T23:54:16.828Z
Learnt from: capcom6
Repo: android-sms-gateway/server PR: 178
File: pkg/cache/redis.go:118-121
Timestamp: 2025-09-10T23:54:16.828Z
Learning: Redis 7.4.0 introduced HEXPIRE, HEXPIREAT, and related commands that support per-field TTL on hash data structures. go-redis v9.6.0+ includes support for these commands with methods like HExpireAt.
Applied to files:
pkg/cache/redis.go
🧬 Code graph analysis (46)
internal/sms-gateway/handlers/middlewares/userauth/userauth.go (1)
internal/sms-gateway/models/models.go (1)
User(18-24)
internal/sms-gateway/modules/auth/module.go (2)
internal/sms-gateway/app.go (1)
Module(36-62)internal/sms-gateway/modules/auth/service.go (2)
New(54-69)Service(39-52)
internal/sms-gateway/modules/metrics/module.go (1)
internal/sms-gateway/app.go (1)
Module(36-62)
internal/sms-gateway/handlers/messages/3rdparty.go (5)
internal/sms-gateway/handlers/logs/3rdparty.go (2)
NewThirdPartyController(24-31)ThirdPartyController(20-22)internal/sms-gateway/handlers/webhooks/3rdparty.go (2)
NewThirdPartyController(32-40)ThirdPartyController(26-30)internal/sms-gateway/handlers/base/handler.go (1)
Handler(15-18)internal/sms-gateway/modules/devices/repository.go (1)
ErrNotFound(13-13)internal/sms-gateway/modules/messages/service.go (1)
EnqueueOptions(26-28)
internal/sms-gateway/modules/events/module.go (1)
internal/sms-gateway/app.go (2)
Module(36-62)Run(64-75)
internal/config/module.go (9)
internal/sms-gateway/app.go (1)
Module(36-62)internal/sms-gateway/modules/sse/config.go (4)
Option(5-5)Config(7-9)NewConfig(19-27)WithKeepAlivePeriod(33-41)internal/config/config.go (13)
Config(10-20)Default(89-127)HTTP(27-33)Database(44-56)Gateway(22-25)Tasks(64-66)API(35-38)GatewayModePublic(6-6)OpenAPI(40-42)Messages(76-79)SSE(72-74)Cache(81-83)PubSub(85-87)internal/sms-gateway/modules/auth/service.go (1)
Config(22-25)internal/sms-gateway/pubsub/config.go (1)
Config(4-7)internal/sms-gateway/modules/messages/config.go (1)
Config(5-8)internal/sms-gateway/handlers/config.go (1)
Config(3-11)internal/sms-gateway/modules/devices/config.go (1)
Config(5-7)internal/sms-gateway/modules/push/types.go (3)
ModeFCM(13-13)Mode(10-10)ModeUpstream(14-14)
pkg/cache/memory_concurrency_test.go (1)
pkg/cache/errors.go (2)
ErrKeyNotFound(9-9)ErrKeyExists(13-13)
pkg/cache/memory_test.go (1)
pkg/cache/errors.go (2)
ErrKeyNotFound(9-9)ErrKeyExists(13-13)
internal/sms-gateway/modules/settings/module.go (1)
internal/sms-gateway/app.go (1)
Module(36-62)
internal/sms-gateway/handlers/settings/mobile.go (1)
internal/sms-gateway/modules/settings/service.go (1)
Service(19-25)
internal/sms-gateway/modules/devices/repository.go (1)
internal/sms-gateway/models/models.go (1)
Device(34-45)
internal/sms-gateway/handlers/webhooks/mobile.go (1)
internal/sms-gateway/modules/webhooks/service.go (1)
Service(28-37)
internal/sms-gateway/modules/webhooks/models.go (2)
internal/sms-gateway/models/models.go (3)
SoftDeletableModel(12-16)User(18-24)Device(34-45)internal/sms-gateway/modules/settings/models.go (1)
Migrate(27-32)
pkg/pubsub/memory.go (2)
pkg/pubsub/options.go (1)
Option(3-3)pkg/pubsub/pubsub.go (2)
Subscription(19-24)PubSub(46-51)
internal/sms-gateway/modules/cleaner/module.go (2)
internal/sms-gateway/app.go (1)
Module(36-62)internal/sms-gateway/modules/messages/module.go (1)
Module(11-35)
internal/sms-gateway/modules/push/fcm/utils.go (2)
internal/sms-gateway/modules/events/types.go (1)
Event(10-13)internal/sms-gateway/modules/push/types.go (1)
Event(17-17)
internal/sms-gateway/modules/push/client.go (1)
internal/sms-gateway/modules/push/types.go (2)
ModeFCM(13-13)ModeUpstream(14-14)
internal/sms-gateway/modules/push/fcm/client.go (2)
internal/sms-gateway/modules/push/fcm/errors.go (1)
ErrInitializationFailed(6-6)internal/sms-gateway/modules/push/upstream/client.go (1)
Client(22-27)
internal/sms-gateway/handlers/settings/3rdparty.go (1)
internal/sms-gateway/modules/settings/models.go (1)
DeviceSettings(10-17)
internal/sms-gateway/models/migration.go (1)
internal/sms-gateway/models/models.go (2)
User(18-24)Device(34-45)
internal/sms-gateway/modules/webhooks/service.go (1)
internal/sms-gateway/modules/webhooks/errors.go (1)
ErrInvalidEvent(9-9)
internal/sms-gateway/modules/webhooks/module.go (4)
internal/sms-gateway/app.go (1)
Module(36-62)internal/sms-gateway/handlers/module.go (1)
Module(15-41)internal/sms-gateway/modules/webhooks/repository.go (1)
NewRepository(12-16)internal/sms-gateway/modules/webhooks/service.go (1)
NewService(39-50)
internal/sms-gateway/modules/devices/service.go (7)
internal/sms-gateway/modules/messages/service.go (2)
NewService(44-67)Service(30-42)internal/sms-gateway/modules/webhooks/service.go (3)
NewService(39-50)ServiceParams(15-26)Service(28-37)internal/sms-gateway/modules/auth/service.go (3)
Service(39-52)Config(22-25)New(54-69)internal/sms-gateway/modules/devices/config.go (1)
Config(5-7)internal/sms-gateway/models/models.go (1)
Device(34-45)internal/sms-gateway/modules/devices/repository_filter.go (4)
SelectFilter(9-9)WithUserID(23-27)WithID(11-15)ActiveWithin(29-33)internal/sms-gateway/modules/devices/repository.go (1)
ErrNotFound(13-13)
internal/sms-gateway/modules/events/service.go (6)
internal/sms-gateway/pubsub/pubsub.go (1)
PubSub(14-14)internal/sms-gateway/modules/events/errors.go (1)
ErrValidationFailed(6-6)internal/sms-gateway/modules/events/metrics.go (6)
DeliveryTypeUnknown(20-20)FailureReasonPublishError(23-23)EventTypeUnknown(26-26)FailureReasonSerializationError(22-22)DeliveryTypePush(18-18)FailureReasonProviderFailed(24-24)internal/sms-gateway/modules/events/types.go (1)
Event(10-13)internal/sms-gateway/modules/push/types.go (1)
Event(17-17)internal/sms-gateway/modules/sse/types.go (1)
Event(7-10)
internal/sms-gateway/handlers/mobile.go (1)
internal/sms-gateway/models/models.go (2)
Device(34-45)User(18-24)
pkg/cache/memory.go (2)
pkg/cache/options.go (3)
GetOption(49-49)AndDelete(89-93)Option(6-6)pkg/cache/cache.go (1)
Cache(5-40)
internal/sms-gateway/modules/devices/module.go (3)
internal/sms-gateway/app.go (1)
Module(36-62)internal/sms-gateway/modules/messages/module.go (1)
Module(11-35)internal/sms-gateway/modules/devices/service.go (3)
ServiceParams(23-33)NewService(46-54)Service(35-44)
internal/sms-gateway/cache/factory.go (2)
pkg/cache/errors.go (1)
ErrInvalidConfig(7-7)internal/config/config.go (1)
Cache(81-83)
internal/sms-gateway/modules/messages/service.go (3)
internal/sms-gateway/modules/messages/repository_filter.go (3)
Order(7-7)SelectFilter(16-23)SelectOptions(51-62)internal/sms-gateway/modules/messages/models.go (3)
MessageState(129-134)ProcessingState(12-12)MessageRecipient(111-117)internal/sms-gateway/modules/messages/errors.go (1)
ValidationError(9-9)
internal/sms-gateway/handlers/webhooks/3rdparty.go (4)
internal/sms-gateway/handlers/devices/3rdparty.go (2)
NewThirdPartyController(24-36)ThirdPartyController(18-22)internal/sms-gateway/handlers/logs/3rdparty.go (2)
NewThirdPartyController(24-31)ThirdPartyController(20-22)internal/sms-gateway/handlers/messages/3rdparty.go (2)
NewThirdPartyController(44-53)ThirdPartyController(37-42)internal/sms-gateway/modules/webhooks/models.go (1)
Webhook(11-25)
internal/sms-gateway/modules/push/module.go (3)
internal/sms-gateway/modules/push/fcm/client.go (2)
New(21-27)Client(14-19)internal/sms-gateway/modules/push/service.go (1)
New(43-73)internal/sms-gateway/modules/push/upstream/client.go (2)
New(29-35)Client(22-27)
internal/sms-gateway/modules/auth/repository.go (1)
internal/sms-gateway/models/models.go (1)
User(18-24)
internal/sms-gateway/modules/messages/cache.go (2)
internal/sms-gateway/modules/messages/domain.go (1)
MessageStateOut(38-44)pkg/cache/options.go (2)
WithTTL(20-31)AndSetTTL(65-69)
internal/sms-gateway/modules/settings/models.go (2)
internal/sms-gateway/models/models.go (2)
TimedModel(7-10)User(18-24)internal/sms-gateway/modules/webhooks/models.go (1)
Migrate(37-42)
internal/sms-gateway/modules/push/upstream/client.go (2)
internal/sms-gateway/modules/push/fcm/client.go (2)
New(21-27)Client(14-19)internal/sms-gateway/modules/push/service.go (1)
New(43-73)
internal/sms-gateway/modules/health/module.go (1)
internal/sms-gateway/modules/health/types.go (1)
Provider(69-75)
internal/sms-gateway/modules/messages/repository.go (4)
internal/sms-gateway/modules/messages/repository_filter.go (3)
SelectFilter(16-23)SelectOptions(51-62)Order(7-7)internal/sms-gateway/modules/messages/models.go (2)
Message(35-55)ProcessingStatePending(16-16)internal/sms-gateway/modules/webhooks/repository_filter.go (1)
WithDeviceID(41-46)internal/sms-gateway/modules/messages/errors.go (1)
ErrLockFailed(6-6)
internal/sms-gateway/handlers/logs/3rdparty.go (3)
internal/sms-gateway/handlers/devices/3rdparty.go (2)
NewThirdPartyController(24-36)ThirdPartyController(18-22)internal/sms-gateway/handlers/messages/3rdparty.go (2)
NewThirdPartyController(44-53)ThirdPartyController(37-42)internal/sms-gateway/handlers/webhooks/3rdparty.go (2)
NewThirdPartyController(32-40)ThirdPartyController(26-30)
internal/sms-gateway/modules/health/health.go (1)
internal/sms-gateway/modules/health/types.go (6)
Checks(67-67)CheckDetail(54-64)Status(7-7)StatusPass(11-11)StatusWarn(12-12)Provider(69-75)
internal/sms-gateway/modules/webhooks/repository.go (1)
internal/sms-gateway/modules/webhooks/models.go (1)
Webhook(11-25)
pkg/cache/memory_bench_test.go (1)
pkg/cache/memory.go (1)
NewMemory(16-23)
internal/sms-gateway/modules/push/types.go (2)
internal/sms-gateway/modules/events/types.go (1)
Event(10-13)internal/sms-gateway/modules/sse/types.go (1)
Event(7-10)
internal/sms-gateway/handlers/devices/3rdparty.go (4)
internal/sms-gateway/modules/devices/service.go (1)
Service(35-44)internal/sms-gateway/handlers/logs/3rdparty.go (2)
NewThirdPartyController(24-31)ThirdPartyController(20-22)internal/sms-gateway/handlers/messages/3rdparty.go (2)
NewThirdPartyController(44-53)ThirdPartyController(37-42)internal/sms-gateway/handlers/webhooks/3rdparty.go (2)
NewThirdPartyController(32-40)ThirdPartyController(26-30)
pkg/cache/redis.go (3)
pkg/cache/errors.go (1)
ErrInvalidConfig(7-7)pkg/cache/options.go (3)
GetOption(49-49)AndDelete(89-93)Option(6-6)pkg/cache/cache.go (1)
Cache(5-40)
internal/sms-gateway/modules/auth/service.go (5)
internal/sms-gateway/online/service.go (2)
New(28-37)Service(14-17)internal/sms-gateway/models/models.go (4)
User(18-24)NewUser(26-32)Device(34-45)NewDevice(47-53)internal/sms-gateway/modules/auth/types.go (1)
OneTimeCode(15-18)pkg/crypto/passwords.go (2)
MakeBCryptHash(14-20)CompareBCryptHash(22-27)internal/sms-gateway/modules/auth/errors.go (1)
ErrAuthorizationFailed(6-6)
internal/sms-gateway/pubsub/pubsub.go (2)
pkg/pubsub/memory.go (1)
NewMemory(24-38)pkg/pubsub/redis.go (2)
NewRedis(42-78)RedisConfig(15-27)
🪛 GitHub Check: Lint
internal/sms-gateway/modules/webhooks/models.go
[failure] 28-28:
webhooks.Webhook is missing fields SoftDeletableModel, ID, User, Device (exhaustruct)
internal/sms-gateway/modules/push/client.go
[failure] 23-23:
do not define dynamic errors, use wrapped static errors instead: "fmt.Errorf("invalid push mode: %s", config.Mode)" (err113)
internal/sms-gateway/pubsub/pubsub.go
[failure] 40-40:
do not define dynamic errors, use wrapped static errors instead: "fmt.Errorf("invalid scheme: %s", u.Scheme)" (err113)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
- GitHub Check: Benchmark
- GitHub Check: E2E
- GitHub Check: Analyze (go)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 0
♻️ Duplicate comments (1)
internal/sms-gateway/models/models.go (1)
26-32: Fix typo in nolint comment.Line 27 still contains the typo "pertial" instead of "partial".
Apply this diff:
- //nolint:exhaustruct // pertial constructor + //nolint:exhaustruct // partial constructor
🧹 Nitpick comments (1)
internal/sms-gateway/pubsub/pubsub.go (1)
35-35: Consider simplifying the tuple assignment syntax.The syntax
pubSub, err = pubsub.NewMemory(opts...), nilis valid but unusual—it pairs a function result with anilliteral in a tuple assignment. For clarity, consider:- pubSub, err = pubsub.NewMemory(opts...), nil + pubSub = pubsub.NewMemory(opts...)Since
erris alreadynilfrom the successfulurl.Parsecall, explicit reassignment is unnecessary. The post-switch error check (line 46) will correctly handle thenilcase.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (4)
internal/sms-gateway/models/models.go(2 hunks)internal/sms-gateway/modules/push/client.go(1 hunks)internal/sms-gateway/modules/webhooks/models.go(1 hunks)internal/sms-gateway/pubsub/pubsub.go(3 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
- internal/sms-gateway/modules/push/client.go
🧰 Additional context used
🧬 Code graph analysis (2)
internal/sms-gateway/pubsub/pubsub.go (3)
internal/config/config.go (1)
PubSub(85-87)pkg/pubsub/memory.go (1)
NewMemory(24-38)pkg/pubsub/redis.go (2)
NewRedis(42-78)RedisConfig(15-27)
internal/sms-gateway/modules/webhooks/models.go (2)
internal/sms-gateway/models/models.go (3)
SoftDeletableModel(12-16)User(18-24)Device(34-45)internal/sms-gateway/modules/settings/models.go (1)
Migrate(27-32)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
- GitHub Check: Benchmark
- GitHub Check: E2E
- GitHub Check: Analyze (go)
🔇 Additional comments (4)
internal/sms-gateway/models/models.go (1)
47-53: LGTM! Typo fixed.The nolint comment typo at line 48 has been corrected to "partial". The constructor properly initializes the specified fields for partial construction.
internal/sms-gateway/modules/webhooks/models.go (2)
27-36: Exhaustruct issue addressed with nolint directive.The
nolint:exhaustructcomment at line 28 addresses the previous exhaustruct lint failure. This is an acceptable approach for partial constructors that intentionally skip fields.
38-43: LGTM! Improved error handling.The migration function now uses consistent patterns:
new(Webhook)for AutoMigrate and proper error wrapping with context. This aligns with migration patterns used elsewhere in the codebase.internal/sms-gateway/pubsub/pubsub.go (1)
17-17: LGTM: Improved error handling with sentinel error.The introduction of
ErrInvalidSchemeas a sentinel error (line 17) and its use in error wrapping (line 43) enables better error inspection viaerrors.Is(). The post-switch error check (lines 46-48) properly handles constructor failures with contextual wrapping.Also applies to: 43-43, 46-50
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 0
🧹 Nitpick comments (1)
pkg/cache/memory_concurrency_test.go (1)
399-437: Consider usingtfor error reporting.While using the blank identifier is technically correct since this test relies solely on the race detector, test functions typically use
t.Errorort.Fatalto report issues. If any of the cache operations encounter critical errors during setup or execution, those would be silently ignored.Consider keeping the
tparameter and adding minimal error checks for critical operations to improve test diagnostics.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (3)
pkg/cache/memory_bench_test.go(19 hunks)pkg/cache/memory_concurrency_test.go(11 hunks)pkg/cache/memory_profile_test.go(2 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
- pkg/cache/memory_bench_test.go
- pkg/cache/memory_profile_test.go
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2025-09-10T11:11:43.462Z
Learnt from: capcom6
Repo: android-sms-gateway/server PR: 178
File: pkg/cache/memory.go:59-74
Timestamp: 2025-09-10T11:11:43.462Z
Learning: In pkg/cache/memory.go, the cleanup method removes expired items first, then executes the provided callback function. This means methods like Drain that use cleanup don't need additional expiration filtering since expired items are already removed before the callback runs.
Applied to files:
pkg/cache/memory_concurrency_test.go
🧬 Code graph analysis (1)
pkg/cache/memory_concurrency_test.go (1)
pkg/cache/errors.go (2)
ErrKeyNotFound(9-9)ErrKeyExists(13-13)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
- GitHub Check: Benchmark
- GitHub Check: E2E
- GitHub Check: Analyze (go)
🔇 Additional comments (3)
pkg/cache/memory_concurrency_test.go (3)
37-40: Excellent error handling improvements.The migration from direct error comparison (
err == cache.ErrKeyNotFound) toerrors.Is(err, cache.ErrKeyNotFound)is a best practice that properly handles wrapped errors. The variable renaming togetErralso improves code clarity.Also applies to: 118-118, 221-228, 298-298, 345-345, 380-380, 393-393
284-288: LGTM: Unused parameter correctly ignored.Using the blank identifier for the unused
keyparameter in the Range callback is the idiomatic approach.
5-5: No issues found.The project specifies Go 1.24.1 in go.mod, which fully supports the range-over-integer syntax (
for range N) used throughout the file. The changes are compatible and follow modern Go practices.
f5d4a1d to
56b30fe
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 2
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (2)
pkg/pubsub/memory.go (1)
54-82: Holding RLock during WaitGroup.Wait() can block unsubscribe operations.The
Publishmethod holds a read lock from line 54 through thewg.Wait()at line 82. During this time, if a subscription is cancelled or closed, the cleanup goroutine inSubscribewill attempt to callunsubscribe, which requires a write lock (line 134). This write lock request will block until the read lock is released, potentially delaying cleanup operations during long-running publishes.Consider copying the subscriber list while holding the lock, then releasing it before spawning goroutines and waiting:
m.mu.RLock() - defer m.mu.RUnlock() subscribers, exists := m.topics[topic] if !exists { + m.mu.RUnlock() return nil } + + // Copy subscribers to avoid holding lock during dispatch + subscribersCopy := make([]subscriber, 0, len(subscribers)) + for _, sub := range subscribers { + subscribersCopy = append(subscribersCopy, sub) + } + m.mu.RUnlock() wg := &sync.WaitGroup{} msg := Message{Topic: topic, Data: data} - for _, sub := range subscribers { + for _, sub := range subscribersCopy { wg.Add(1) go func(sub subscriber) { defer wg.Done()internal/sms-gateway/modules/settings/repository.go (1)
41-45: Avoid mutating the caller’s settings on merge failure.
appendMapassigns intosettings.Settingseven when it returns an error, because both LHS targets are set before the error check. If validation fails, we bail out but the caller’s struct keeps the (possibly nil) merged map, losing the original payload even though the transaction rolls back. Please stage the merge first and only assign once the error check passes.- var err error - settings.Settings, err = appendMap(source.Settings, settings.Settings, rules) - if err != nil { - return err - } + merged, err := appendMap(source.Settings, settings.Settings, rules) + if err != nil { + return err + } + settings.Settings = merged
♻️ Duplicate comments (8)
.github/workflows/pr.yml (2)
92-92: Pin the S3 upload action to a specific version.The custom S3 upload action uses an unpinned
masterbranch, which introduces supply-chain risk and could break silently if the action changes unexpectedly.Apply this diff to pin to a stable version tag:
- uses: capcom6/upload-s3-action@master + uses: capcom6/upload-s3-action@v1.0.0(Replace
v1.0.0with the appropriate stable version from the action's releases.)
111-118:⚠️ Docker image tagging logic may fail.Lines 111–114 attempt to tag and push images with
latest-arm64andlatest-amd64names, but when GoReleaser runs with--snapshot, it automatically adds the platform suffix to tags. The exact tag format depends on your.goreleaser.yamlconfiguration. If GoReleaser does not producelatest-arm64andlatest-amd64tags, thedocker tagcommands will fail.Please verify the image tags that GoReleaser actually produces by inspecting your
.goreleaser.yamlconfiguration. Confirm that thetagsindockers_v2are configured to producelatest-arm64andlatest-amd64, or update this step to use the correct tag names GoReleaser generates.If GoReleaser's default tag templates don't include per-arch tags, you may need to either:
- Update
.goreleaser.yamlto explicitly define per-arch tag templates, or- Refactor this step to load and re-tag the images that GoReleaser actually produces.
internal/sms-gateway/handlers/mobile.go (1)
275-276: Handle the ID generator error path.Line 276 still drops the error from
nanoid.Standard. Even though size 21 is safe today, a future tweak (or an upstream contract change) would make this silently fail and leave us without diagnostics—the same concern raised in the earlier review. Please handle the error explicitly or document why it is impossible.- const idGenSize = 21 - idGen, _ := nanoid.Standard(idGenSize) + const idGenSize = 21 + idGen, err := nanoid.Standard(idGenSize) + if err != nil { + params.Logger.Fatal("failed to initialize ID generator", zap.Error(err)) + }internal/sms-gateway/models/models.go (1)
26-31: Fix typo in nolint noteLine 27 still reads “pertial constructor”, reintroducing the earlier typo. Please switch it back to “partial” to keep the lint waiver description correct.
- //nolint:exhaustruct // pertial constructor + //nolint:exhaustruct // partial constructorinternal/config/module.go (2)
27-37: Wrap private Config provider with fx.Annotate.This issue was previously flagged. Passing
fx.Privateas a separate argument causes Fx to treat it as a constructor, leading to startup errors. Wrap the provider withfx.Annotate(..., fx.Private)instead.Apply this diff:
- fx.Provide( - func(log *zap.Logger) Config { + fx.Provide( + fx.Annotate( + func(log *zap.Logger) Config { defaultConfig := Default() if err := config.LoadConfig(&defaultConfig); err != nil { log.Error("Error loading config", zap.Error(err)) } return defaultConfig - }, - fx.Private, + }, + fx.Private, + ), ),
94-103: Don't override API path when only host is omitted.This issue was previously flagged. The condition resets
cfg.HTTP.API.Pathto/apiwhenever the host is blank, which overwrites any explicit path users configured. Key off the path field itself so user-supplied values survive.Apply this diff:
- if cfg.HTTP.API.Host == "" { + if cfg.HTTP.API.Path == "" { cfg.HTTP.API.Path = "/api" }internal/sms-gateway/modules/auth/service.go (1)
55-56: Document or handle the ignored error.This issue was previously flagged. The error from
nanoid.Standard(idLen)is silently discarded. While21is a valid size, add a comment explaining why it's safe to ignore.Apply this diff:
const idLen = 21 + // nanoid.Standard with size 21 never returns an error idgen, _ := nanoid.Standard(idLen)internal/sms-gateway/modules/messages/repository.go (1)
185-185: Prevent panic from double%wwrapping.This issue was previously flagged. Line 185 uses
fmt.Errorf("%w: %w", ErrLockFailed, err). Thefmt.Errorffunction permits only one%w, so hitting this path will panic instead of returning an error. Drop the second%w(use%vinstead).Apply this diff:
- return fmt.Errorf("%w: %w", ErrLockFailed, err) + return fmt.Errorf("%w: %v", ErrLockFailed, err)
🧹 Nitpick comments (7)
pkg/pubsub/memory.go (1)
33-34: Remove redundant zero-value initialization.The explicit initialization of
sync.WaitGroup{}andsync.RWMutex{}is unnecessary since these types are automatically zero-initialized when the struct is created.Apply this diff to simplify the code:
return &MemoryPubSub{ bufferSize: o.bufferSize, - wg: sync.WaitGroup{}, - mu: sync.RWMutex{}, topics: make(map[string]map[string]subscriber), closeCh: make(chan struct{}), }internal/sms-gateway/modules/db/health.go (1)
20-21: Minor: Explicit initialization is redundant but harmless.The
failedPings: atomic.Int64{}initialization is unnecessary since the zero value ofatomic.Int64is already properly initialized. However, this explicit form improves clarity.You can simplify to:
func newHealth(db *sql.DB) *health { return &health{ db: db, - - failedPings: atomic.Int64{}, } }internal/sms-gateway/handlers/middlewares/userauth/userauth.go (1)
90-91: Base HasUser on GetUser to avoid false positives.With
GetUsernow returning*models.User, a typed-nil or mismatched Locals entry keepsHasUserreturning true even thoughGetUseryields nil.UserRequiredwould then pass the request while downstream handlers see a nil user and can panic on dereference. Please flipHasUserto rely onGetUser(c) != nilso the guard reflects the actual availability of a usable user pointer.func HasUser(c *fiber.Ctx) bool { return GetUser(c) != nil }internal/sms-gateway/handlers/settings/3rdparty.go (2)
85-85: Consider pre-sizing the map for efficiency.The map was previously pre-sized to 8. If the settings typically contain around 8 keys, pre-sizing can reduce allocations.
- settings := make(map[string]any) + settings := make(map[string]any, 8)
119-119: Consider pre-sizing the map for efficiency.Similar to the
putmethod, pre-sizing to the expected capacity can reduce allocations.- settings := make(map[string]any) + settings := make(map[string]any, 8)internal/sms-gateway/handlers/webhooks/3rdparty.go (1)
77-83: Consider consistent pointer usage.The DTO is created as a pointer but dereferenced when passed to the service. If the service expects a value type, consider using a value type throughout for consistency, or adjust the service to accept a pointer.
- dto := new(smsgateway.Webhook) + dto := smsgateway.Webhook{} - if err := h.BodyParserValidator(c, dto); err != nil { + if err := h.BodyParserValidator(c, &dto); err != nil { return fiber.NewError(fiber.StatusBadRequest, err.Error()) } - if err := h.webhooksSvc.Replace(user.ID, *dto); err != nil { + if err := h.webhooksSvc.Replace(user.ID, dto); err != nil {internal/config/module.go (1)
120-124: Address the TODO: make UnusedLifetime configurable.The
UnusedLifetimeis currently hardcoded to 365 days. Consider adding a configuration field to make this value adjustable.Would you like me to generate a solution that adds a configurable field for this value?
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
⛔ Files ignored due to path filters (1)
go.sumis excluded by!**/*.sum
📒 Files selected for processing (107)
.github/workflows/docker-build.yml(0 hunks).github/workflows/docker-publish.yml(0 hunks).github/workflows/go.yml(3 hunks).github/workflows/pr.yml(1 hunks).github/workflows/release.yml(2 hunks).gitignore(1 hunks).golangci.yml(1 hunks).goreleaser.yaml(1 hunks)Dockerfile.goreleaser(1 hunks)Makefile(2 hunks)internal/config/config.go(3 hunks)internal/config/module.go(1 hunks)internal/sms-gateway/app.go(2 hunks)internal/sms-gateway/cache/errors.go(1 hunks)internal/sms-gateway/cache/factory.go(2 hunks)internal/sms-gateway/handlers/base/handler_test.go(5 hunks)internal/sms-gateway/handlers/converters/messages.go(1 hunks)internal/sms-gateway/handlers/devices/3rdparty.go(3 hunks)internal/sms-gateway/handlers/events/mobile.go(1 hunks)internal/sms-gateway/handlers/health.go(2 hunks)internal/sms-gateway/handlers/logs/3rdparty.go(2 hunks)internal/sms-gateway/handlers/messages/3rdparty.go(8 hunks)internal/sms-gateway/handlers/messages/mobile.go(2 hunks)internal/sms-gateway/handlers/messages/params.go(3 hunks)internal/sms-gateway/handlers/middlewares/userauth/userauth.go(2 hunks)internal/sms-gateway/handlers/mobile.go(8 hunks)internal/sms-gateway/handlers/module.go(1 hunks)internal/sms-gateway/handlers/root.go(1 hunks)internal/sms-gateway/handlers/settings/3rdparty.go(4 hunks)internal/sms-gateway/handlers/settings/mobile.go(2 hunks)internal/sms-gateway/handlers/upstream.go(3 hunks)internal/sms-gateway/handlers/webhooks/3rdparty.go(4 hunks)internal/sms-gateway/handlers/webhooks/mobile.go(2 hunks)internal/sms-gateway/models/migration.go(2 hunks)internal/sms-gateway/models/models.go(2 hunks)internal/sms-gateway/models/module.go(1 hunks)internal/sms-gateway/modules/auth/errors.go(1 hunks)internal/sms-gateway/modules/auth/module.go(1 hunks)internal/sms-gateway/modules/auth/repository.go(1 hunks)internal/sms-gateway/modules/auth/service.go(6 hunks)internal/sms-gateway/modules/auth/types.go(1 hunks)internal/sms-gateway/modules/cleaner/module.go(1 hunks)internal/sms-gateway/modules/cleaner/service.go(2 hunks)internal/sms-gateway/modules/db/health.go(3 hunks)internal/sms-gateway/modules/db/module.go(2 hunks)internal/sms-gateway/modules/devices/module.go(1 hunks)internal/sms-gateway/modules/devices/repository.go(3 hunks)internal/sms-gateway/modules/devices/repository_filter.go(1 hunks)internal/sms-gateway/modules/devices/service.go(6 hunks)internal/sms-gateway/modules/events/errors.go(1 hunks)internal/sms-gateway/modules/events/metrics.go(3 hunks)internal/sms-gateway/modules/events/module.go(1 hunks)internal/sms-gateway/modules/events/service.go(6 hunks)internal/sms-gateway/modules/events/types.go(2 hunks)internal/sms-gateway/modules/health/health.go(2 hunks)internal/sms-gateway/modules/health/module.go(2 hunks)internal/sms-gateway/modules/health/service.go(3 hunks)internal/sms-gateway/modules/health/types.go(3 hunks)internal/sms-gateway/modules/messages/cache.go(2 hunks)internal/sms-gateway/modules/messages/converters.go(1 hunks)internal/sms-gateway/modules/messages/errors.go(1 hunks)internal/sms-gateway/modules/messages/models.go(6 hunks)internal/sms-gateway/modules/messages/module.go(1 hunks)internal/sms-gateway/modules/messages/repository.go(5 hunks)internal/sms-gateway/modules/messages/repository_filter.go(2 hunks)internal/sms-gateway/modules/messages/service.go(11 hunks)internal/sms-gateway/modules/messages/service_test.go(0 hunks)internal/sms-gateway/modules/metrics/handler.go(1 hunks)internal/sms-gateway/modules/metrics/module.go(1 hunks)internal/sms-gateway/modules/push/client.go(1 hunks)internal/sms-gateway/modules/push/client/types.go(1 hunks)internal/sms-gateway/modules/push/fcm/client.go(4 hunks)internal/sms-gateway/modules/push/fcm/errors.go(1 hunks)internal/sms-gateway/modules/push/fcm/utils.go(1 hunks)internal/sms-gateway/modules/push/module.go(2 hunks)internal/sms-gateway/modules/push/service.go(11 hunks)internal/sms-gateway/modules/push/types.go(3 hunks)internal/sms-gateway/modules/push/upstream/client.go(5 hunks)internal/sms-gateway/modules/settings/models.go(1 hunks)internal/sms-gateway/modules/settings/module.go(1 hunks)internal/sms-gateway/modules/settings/repository.go(5 hunks)internal/sms-gateway/modules/settings/service.go(2 hunks)internal/sms-gateway/modules/settings/utils.go(4 hunks)internal/sms-gateway/modules/sse/config.go(2 hunks)internal/sms-gateway/modules/sse/errors.go(1 hunks)internal/sms-gateway/modules/sse/metrics.go(3 hunks)internal/sms-gateway/modules/sse/module.go(1 hunks)internal/sms-gateway/modules/sse/service.go(8 hunks)internal/sms-gateway/modules/webhooks/errors.go(3 hunks)internal/sms-gateway/modules/webhooks/models.go(1 hunks)internal/sms-gateway/modules/webhooks/module.go(1 hunks)internal/sms-gateway/modules/webhooks/repository.go(2 hunks)internal/sms-gateway/modules/webhooks/repository_filter.go(1 hunks)internal/sms-gateway/modules/webhooks/service.go(2 hunks)internal/sms-gateway/openapi/docs.go(5 hunks)internal/sms-gateway/pubsub/module.go(2 hunks)internal/sms-gateway/pubsub/pubsub.go(3 hunks)pkg/cache/errors.go(1 hunks)pkg/cache/memory.go(12 hunks)pkg/cache/memory_bench_test.go(19 hunks)pkg/cache/memory_concurrency_test.go(12 hunks)pkg/cache/memory_edge_test.go(8 hunks)pkg/cache/memory_profile_test.go(2 hunks)pkg/cache/memory_test.go(8 hunks)pkg/cache/options.go(1 hunks)pkg/cache/redis.go(15 hunks)pkg/pubsub/memory.go(8 hunks)
⛔ Files not processed due to max files limit (5)
- pkg/pubsub/options.go
- pkg/pubsub/pubsub.go
- pkg/pubsub/redis.go
- test/e2e/mobile_test.go
- test/e2e/utils_test.go
💤 Files with no reviewable changes (3)
- internal/sms-gateway/modules/messages/service_test.go
- .github/workflows/docker-build.yml
- .github/workflows/docker-publish.yml
✅ Files skipped from review due to trivial changes (1)
- internal/sms-gateway/modules/webhooks/repository_filter.go
🚧 Files skipped from review as they are similar to previous changes (39)
- internal/sms-gateway/modules/settings/service.go
- internal/sms-gateway/modules/events/errors.go
- internal/sms-gateway/modules/sse/module.go
- internal/sms-gateway/modules/push/fcm/utils.go
- internal/sms-gateway/handlers/settings/mobile.go
- internal/sms-gateway/modules/sse/errors.go
- internal/sms-gateway/modules/cleaner/service.go
- .github/workflows/go.yml
- internal/sms-gateway/modules/events/types.go
- internal/sms-gateway/modules/metrics/handler.go
- internal/sms-gateway/handlers/logs/3rdparty.go
- internal/sms-gateway/handlers/health.go
- internal/sms-gateway/modules/webhooks/errors.go
- internal/sms-gateway/modules/webhooks/module.go
- internal/sms-gateway/handlers/events/mobile.go
- internal/sms-gateway/handlers/module.go
- pkg/cache/memory_test.go
- internal/sms-gateway/modules/push/fcm/client.go
- internal/sms-gateway/modules/devices/repository_filter.go
- internal/sms-gateway/modules/messages/module.go
- internal/sms-gateway/handlers/messages/params.go
- internal/sms-gateway/cache/errors.go
- internal/sms-gateway/modules/devices/repository.go
- internal/sms-gateway/models/module.go
- internal/sms-gateway/pubsub/pubsub.go
- Dockerfile.goreleaser
- internal/sms-gateway/modules/push/client.go
- internal/sms-gateway/cache/factory.go
- internal/sms-gateway/handlers/devices/3rdparty.go
- pkg/cache/memory.go
- internal/sms-gateway/modules/webhooks/repository.go
- internal/sms-gateway/modules/settings/models.go
- .goreleaser.yaml
- .gitignore
- internal/sms-gateway/pubsub/module.go
- pkg/cache/options.go
- pkg/cache/memory_bench_test.go
- internal/sms-gateway/modules/health/health.go
- internal/sms-gateway/handlers/root.go
🧰 Additional context used
🧠 Learnings (6)
📚 Learning: 2025-09-10T23:53:42.006Z
Learnt from: capcom6
Repo: android-sms-gateway/server PR: 178
File: pkg/cache/redis.go:63-75
Timestamp: 2025-09-10T23:53:42.006Z
Learning: In pkg/cache/redis.go, the Redis cache implementation uses Redis's native field-level TTL via HExpireAt, meaning expired fields are automatically removed by Redis itself. This differs from the memory implementation where the application tracks expiration times. As a result, operations like Drain that call HGetAll will only return non-expired items since expired fields are already removed by Redis.
Applied to files:
pkg/cache/memory_edge_test.gopkg/cache/redis.go
📚 Learning: 2025-09-10T11:11:43.462Z
Learnt from: capcom6
Repo: android-sms-gateway/server PR: 178
File: pkg/cache/memory.go:59-74
Timestamp: 2025-09-10T11:11:43.462Z
Learning: In pkg/cache/memory.go, the cleanup method removes expired items first, then executes the provided callback function. This means methods like Drain that use cleanup don't need additional expiration filtering since expired items are already removed before the callback runs.
Applied to files:
pkg/cache/memory_edge_test.gopkg/cache/memory_concurrency_test.gopkg/cache/redis.gopkg/cache/memory_profile_test.go
📚 Learning: 2025-09-10T23:45:45.502Z
Learnt from: capcom6
Repo: android-sms-gateway/server PR: 178
File: internal/sms-gateway/online/module.go:14-16
Timestamp: 2025-09-10T23:45:45.502Z
Learning: fx.Decorate in uber-go/fx creates module-scoped decorated dependencies, not global modifications. Using fx.Decorate(func(log *zap.Logger) *zap.Logger { return log.Named("module_name") }) is a correct pattern for creating module-specific loggers that are scoped only to that module's dependency graph.
Applied to files:
internal/sms-gateway/modules/metrics/module.gointernal/sms-gateway/modules/auth/module.gointernal/sms-gateway/modules/events/module.gointernal/config/module.gointernal/sms-gateway/modules/devices/module.gointernal/sms-gateway/modules/settings/module.gointernal/sms-gateway/modules/cleaner/module.go
📚 Learning: 2025-09-10T23:54:16.828Z
Learnt from: capcom6
Repo: android-sms-gateway/server PR: 178
File: pkg/cache/redis.go:118-121
Timestamp: 2025-09-10T23:54:16.828Z
Learning: Redis 7.4.0 introduced HEXPIRE, HEXPIREAT, and related commands that support per-field TTL on hash data structures. go-redis v9.6.0+ includes support for these commands with methods like HExpireAt.
Applied to files:
pkg/cache/redis.go
📚 Learning: 2025-07-22T00:31:28.796Z
Learnt from: capcom6
Repo: android-sms-gateway/server PR: 154
File: internal/sms-gateway/modules/sse/service.go:67-79
Timestamp: 2025-07-22T00:31:28.796Z
Learning: In the SSE service Send method (internal/sms-gateway/modules/sse/service.go), blocking on full channels is intentional and acceptable. Timeout control is handled at the calling code level through context, not within the Send method itself. The user prefers this approach over adding a default case that would drop messages when channels are full.
Applied to files:
internal/sms-gateway/modules/sse/service.gointernal/sms-gateway/modules/events/service.go
📚 Learning: 2025-07-22T00:34:31.896Z
Learnt from: capcom6
Repo: android-sms-gateway/server PR: 154
File: internal/sms-gateway/modules/sse/service.go:89-92
Timestamp: 2025-07-22T00:34:31.896Z
Learning: In Go SSE implementations, when cleaning up connections with buffered channels, avoid closing the data channels directly as this can cause panics if concurrent goroutines attempt to write to them. Instead, use a separate close signal channel to notify writers that the connection should be terminated, allowing the data channels to be garbage collected naturally when the connection is removed.
Applied to files:
internal/sms-gateway/modules/sse/service.go
🧬 Code graph analysis (43)
internal/config/config.go (4)
internal/sms-gateway/modules/sse/config.go (1)
Config(7-9)internal/sms-gateway/pubsub/config.go (1)
Config(4-7)internal/sms-gateway/modules/messages/config.go (1)
Config(5-8)internal/sms-gateway/cache/config.go (1)
Config(4-6)
pkg/cache/memory_edge_test.go (1)
pkg/cache/errors.go (2)
ErrKeyExpired(11-11)ErrKeyNotFound(9-9)
internal/sms-gateway/modules/push/module.go (3)
internal/sms-gateway/modules/push/fcm/client.go (2)
New(21-27)Client(14-19)internal/sms-gateway/modules/push/service.go (1)
New(43-73)internal/sms-gateway/modules/push/upstream/client.go (2)
New(29-35)Client(22-27)
internal/sms-gateway/modules/messages/cache.go (2)
internal/sms-gateway/modules/messages/domain.go (1)
MessageStateOut(38-44)pkg/cache/options.go (2)
WithTTL(20-31)AndSetTTL(65-69)
internal/sms-gateway/modules/metrics/module.go (1)
internal/sms-gateway/app.go (1)
Module(36-62)
internal/sms-gateway/modules/auth/module.go (1)
internal/sms-gateway/app.go (1)
Module(36-62)
internal/sms-gateway/handlers/settings/3rdparty.go (1)
internal/sms-gateway/modules/settings/models.go (1)
DeviceSettings(10-17)
internal/sms-gateway/modules/events/module.go (2)
internal/sms-gateway/app.go (2)
Module(36-62)Run(64-75)internal/sms-gateway/modules/events/service.go (2)
NewService(33-52)Service(20-31)
internal/config/module.go (9)
internal/sms-gateway/app.go (1)
Module(36-62)internal/sms-gateway/modules/sse/config.go (4)
Option(5-5)Config(7-9)NewConfig(19-27)WithKeepAlivePeriod(33-41)internal/config/config.go (14)
Config(10-20)Default(89-127)HTTP(27-33)Database(44-56)Gateway(22-25)GatewayModePrivate(7-7)Tasks(64-66)API(35-38)GatewayModePublic(6-6)OpenAPI(40-42)Messages(76-79)SSE(72-74)Cache(81-83)PubSub(85-87)internal/sms-gateway/modules/auth/service.go (1)
Config(22-25)internal/sms-gateway/modules/push/service.go (1)
Config(23-30)internal/sms-gateway/pubsub/config.go (1)
Config(4-7)internal/sms-gateway/modules/messages/config.go (1)
Config(5-8)internal/sms-gateway/cache/config.go (1)
Config(4-6)internal/sms-gateway/handlers/config.go (1)
Config(3-11)
internal/sms-gateway/modules/push/upstream/client.go (2)
internal/sms-gateway/modules/push/fcm/client.go (2)
New(21-27)Client(14-19)internal/sms-gateway/modules/push/service.go (1)
New(43-73)
internal/sms-gateway/modules/devices/module.go (4)
internal/sms-gateway/app.go (1)
Module(36-62)internal/sms-gateway/modules/messages/module.go (1)
Module(11-35)internal/sms-gateway/modules/devices/service.go (3)
ServiceParams(23-33)NewService(46-54)Service(35-44)internal/sms-gateway/modules/webhooks/service.go (3)
ServiceParams(15-26)NewService(39-50)Service(28-37)
internal/sms-gateway/models/migration.go (1)
internal/sms-gateway/models/models.go (2)
User(18-24)Device(34-45)
internal/sms-gateway/modules/sse/config.go (1)
internal/config/config.go (1)
Config(10-20)
internal/sms-gateway/handlers/middlewares/userauth/userauth.go (1)
internal/sms-gateway/models/models.go (1)
User(18-24)
internal/sms-gateway/handlers/messages/mobile.go (2)
internal/sms-gateway/handlers/base/handler.go (1)
Handler(15-18)internal/sms-gateway/models/models.go (1)
Device(34-45)
internal/sms-gateway/handlers/messages/3rdparty.go (4)
internal/sms-gateway/handlers/devices/3rdparty.go (2)
NewThirdPartyController(24-36)ThirdPartyController(18-22)internal/sms-gateway/handlers/base/handler.go (1)
Handler(15-18)internal/sms-gateway/modules/devices/repository.go (1)
ErrNotFound(13-13)internal/sms-gateway/modules/messages/service.go (1)
EnqueueOptions(26-28)
internal/sms-gateway/modules/settings/module.go (1)
internal/sms-gateway/app.go (1)
Module(36-62)
internal/sms-gateway/modules/health/service.go (1)
internal/sms-gateway/modules/health/types.go (3)
Provider(69-75)Checks(67-67)CheckResult(21-24)
pkg/pubsub/memory.go (2)
pkg/pubsub/options.go (1)
Option(3-3)pkg/pubsub/pubsub.go (2)
Subscription(19-24)PubSub(46-51)
internal/sms-gateway/handlers/webhooks/3rdparty.go (4)
internal/sms-gateway/handlers/devices/3rdparty.go (2)
NewThirdPartyController(24-36)ThirdPartyController(18-22)internal/sms-gateway/handlers/logs/3rdparty.go (2)
NewThirdPartyController(24-31)ThirdPartyController(20-22)internal/sms-gateway/handlers/messages/3rdparty.go (2)
NewThirdPartyController(44-53)ThirdPartyController(37-42)internal/sms-gateway/modules/webhooks/models.go (1)
Webhook(11-25)
internal/sms-gateway/modules/cleaner/module.go (2)
internal/sms-gateway/app.go (1)
Module(36-62)internal/sms-gateway/modules/messages/module.go (1)
Module(11-35)
internal/sms-gateway/handlers/webhooks/mobile.go (1)
internal/sms-gateway/modules/webhooks/service.go (1)
Service(28-37)
internal/sms-gateway/modules/messages/models.go (1)
internal/sms-gateway/models/models.go (1)
SoftDeletableModel(12-16)
pkg/cache/memory_concurrency_test.go (1)
pkg/cache/errors.go (2)
ErrKeyNotFound(9-9)ErrKeyExists(13-13)
internal/sms-gateway/modules/webhooks/service.go (1)
internal/sms-gateway/modules/webhooks/errors.go (1)
ErrInvalidEvent(9-9)
internal/sms-gateway/modules/messages/service.go (3)
internal/sms-gateway/modules/messages/repository_filter.go (3)
Order(7-7)SelectFilter(16-23)SelectOptions(51-62)internal/sms-gateway/modules/messages/models.go (3)
MessageState(129-134)ProcessingState(12-12)MessageRecipient(111-117)internal/sms-gateway/modules/messages/errors.go (1)
ValidationError(9-9)
internal/sms-gateway/handlers/mobile.go (1)
internal/sms-gateway/models/models.go (2)
Device(34-45)User(18-24)
internal/sms-gateway/modules/auth/service.go (5)
internal/sms-gateway/online/service.go (2)
New(28-37)Service(14-17)internal/sms-gateway/models/models.go (4)
User(18-24)NewUser(26-32)Device(34-45)NewDevice(47-53)internal/sms-gateway/modules/auth/types.go (1)
OneTimeCode(15-18)pkg/crypto/passwords.go (2)
MakeBCryptHash(14-20)CompareBCryptHash(22-27)internal/sms-gateway/modules/auth/errors.go (1)
ErrAuthorizationFailed(6-6)
internal/sms-gateway/modules/push/types.go (2)
internal/sms-gateway/modules/events/types.go (1)
Event(10-13)internal/sms-gateway/modules/sse/types.go (1)
Event(7-10)
internal/sms-gateway/modules/messages/repository_filter.go (2)
internal/sms-gateway/modules/webhooks/repository_filter.go (2)
WithExtID(7-11)WithDeviceID(41-46)internal/sms-gateway/modules/messages/models.go (1)
ProcessingState(12-12)
internal/sms-gateway/modules/messages/repository.go (3)
internal/sms-gateway/modules/messages/repository_filter.go (3)
SelectFilter(16-23)SelectOptions(51-62)Order(7-7)internal/sms-gateway/modules/messages/models.go (2)
Message(35-55)ProcessingStatePending(16-16)internal/sms-gateway/modules/messages/errors.go (1)
ErrLockFailed(6-6)
internal/sms-gateway/modules/push/service.go (3)
internal/sms-gateway/modules/push/fcm/client.go (1)
Client(14-19)internal/sms-gateway/modules/push/upstream/client.go (1)
Client(22-27)internal/sms-gateway/modules/push/types.go (1)
Event(17-17)
internal/sms-gateway/modules/db/health.go (1)
internal/sms-gateway/modules/health/types.go (2)
Checks(67-67)Provider(69-75)
pkg/cache/redis.go (3)
pkg/cache/errors.go (1)
ErrInvalidConfig(7-7)pkg/cache/options.go (3)
GetOption(49-49)AndDelete(89-93)Option(6-6)pkg/cache/cache.go (1)
Cache(5-40)
internal/sms-gateway/modules/auth/repository.go (1)
internal/sms-gateway/models/models.go (1)
User(18-24)
internal/sms-gateway/modules/sse/service.go (2)
internal/sms-gateway/modules/sse/errors.go (1)
ErrNoConnection(6-6)internal/sms-gateway/modules/sse/metrics.go (1)
ErrorTypeWriteFailure(21-21)
internal/sms-gateway/modules/webhooks/models.go (2)
internal/sms-gateway/models/models.go (3)
SoftDeletableModel(12-16)User(18-24)Device(34-45)internal/sms-gateway/modules/settings/models.go (1)
Migrate(27-32)
pkg/cache/memory_profile_test.go (1)
pkg/cache/memory.go (1)
NewMemory(16-23)
internal/sms-gateway/modules/health/module.go (1)
internal/sms-gateway/modules/health/types.go (1)
Provider(69-75)
internal/sms-gateway/app.go (16)
internal/config/module.go (1)
Module(23-143)internal/sms-gateway/handlers/module.go (1)
Module(15-41)internal/sms-gateway/modules/auth/module.go (1)
Module(10-32)internal/sms-gateway/modules/cleaner/module.go (1)
Module(28-38)internal/sms-gateway/modules/db/module.go (1)
Module(16-26)internal/sms-gateway/modules/devices/module.go (1)
Module(16-35)internal/sms-gateway/modules/events/module.go (1)
Module(10-39)internal/sms-gateway/modules/health/module.go (1)
Module(8-22)internal/sms-gateway/modules/messages/module.go (1)
Module(11-35)internal/sms-gateway/modules/metrics/module.go (1)
Module(9-19)internal/sms-gateway/modules/push/module.go (1)
Module(11-36)internal/sms-gateway/modules/settings/module.go (1)
Module(9-23)internal/sms-gateway/modules/sse/module.go (1)
Module(10-32)internal/sms-gateway/modules/webhooks/module.go (1)
Module(9-20)internal/sms-gateway/pubsub/module.go (1)
Module(11-30)internal/sms-gateway/online/module.go (1)
Module(11-36)
internal/sms-gateway/modules/devices/service.go (4)
internal/sms-gateway/models/models.go (1)
Device(34-45)internal/sms-gateway/modules/db/module.go (1)
IDGen(14-14)internal/sms-gateway/modules/devices/repository_filter.go (4)
SelectFilter(9-9)WithUserID(23-27)WithID(11-15)ActiveWithin(29-33)internal/sms-gateway/modules/devices/repository.go (1)
ErrNotFound(13-13)
internal/sms-gateway/modules/settings/repository.go (1)
internal/sms-gateway/modules/settings/models.go (1)
DeviceSettings(10-17)
internal/sms-gateway/modules/events/service.go (6)
internal/sms-gateway/pubsub/pubsub.go (1)
PubSub(15-15)internal/sms-gateway/modules/events/errors.go (1)
ErrValidationFailed(6-6)internal/sms-gateway/modules/events/metrics.go (6)
DeliveryTypeUnknown(20-20)FailureReasonPublishError(23-23)EventTypeUnknown(26-26)FailureReasonSerializationError(22-22)DeliveryTypePush(18-18)FailureReasonProviderFailed(24-24)internal/sms-gateway/modules/events/types.go (1)
Event(10-13)internal/sms-gateway/modules/push/types.go (1)
Event(17-17)internal/sms-gateway/modules/sse/types.go (1)
Event(7-10)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
- GitHub Check: Benchmark
- GitHub Check: E2E
- GitHub Check: Analyze (go)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 0
🧹 Nitpick comments (2)
internal/sms-gateway/modules/sse/service.go (2)
46-46: Optional: Remove redundant RWMutex initialization.In Go, the zero value of
sync.RWMutexis already ready to use. Explicit initialization withsync.RWMutex{}is redundant and can be removed.Apply this diff:
- mu: sync.RWMutex{}, + mu: sync.RWMutex,Or simply rely on the field declaration without initialization.
181-192: Optional: Consider distinguishing write vs. flush failures in metrics.The error wrapping and handling are correct. Both write and flush failures increment
ErrorTypeWriteFailure. For enhanced debugging, you might consider using separate error types (e.g.,ErrorTypeWriteFailureandErrorTypeFlushFailure) to distinguish between the two failure modes.Example:
func (s *Service) writeToStream(w *bufio.Writer, data string) error { if _, err := fmt.Fprintf(w, "%s\n\n", data); err != nil { s.metrics.IncrementConnectionErrors(ErrorTypeWriteFailure) return fmt.Errorf("can't write to stream: %w", err) } if err := w.Flush(); err != nil { - s.metrics.IncrementConnectionErrors(ErrorTypeWriteFailure) + s.metrics.IncrementConnectionErrors(ErrorTypeFlushFailure) return fmt.Errorf("can't flush stream: %w", err) } return nil }(This would require adding
ErrorTypeFlushFailureto the metrics constants.)
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (2)
internal/sms-gateway/handlers/middlewares/userauth/userauth.go(2 hunks)internal/sms-gateway/modules/sse/service.go(8 hunks)
🧰 Additional context used
🧠 Learnings (2)
📚 Learning: 2025-07-22T00:31:28.796Z
Learnt from: capcom6
Repo: android-sms-gateway/server PR: 154
File: internal/sms-gateway/modules/sse/service.go:67-79
Timestamp: 2025-07-22T00:31:28.796Z
Learning: In the SSE service Send method (internal/sms-gateway/modules/sse/service.go), blocking on full channels is intentional and acceptable. Timeout control is handled at the calling code level through context, not within the Send method itself. The user prefers this approach over adding a default case that would drop messages when channels are full.
Applied to files:
internal/sms-gateway/modules/sse/service.go
📚 Learning: 2025-07-22T00:34:31.896Z
Learnt from: capcom6
Repo: android-sms-gateway/server PR: 154
File: internal/sms-gateway/modules/sse/service.go:89-92
Timestamp: 2025-07-22T00:34:31.896Z
Learning: In Go SSE implementations, when cleaning up connections with buffered channels, avoid closing the data channels directly as this can cause panics if concurrent goroutines attempt to write to them. Instead, use a separate close signal channel to notify writers that the connection should be terminated, allowing the data channels to be garbage collected naturally when the connection is removed.
Applied to files:
internal/sms-gateway/modules/sse/service.go
🧬 Code graph analysis (2)
internal/sms-gateway/handlers/middlewares/userauth/userauth.go (1)
internal/sms-gateway/models/models.go (1)
User(18-24)
internal/sms-gateway/modules/sse/service.go (2)
internal/sms-gateway/modules/sse/errors.go (1)
ErrNoConnection(6-6)internal/sms-gateway/modules/sse/metrics.go (1)
ErrorTypeWriteFailure(21-21)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
- GitHub Check: E2E
- GitHub Check: Benchmark
- GitHub Check: Analyze (go)
🔇 Additional comments (8)
internal/sms-gateway/handlers/middlewares/userauth/userauth.go (3)
89-91: LGTM! Clean refactoring that delegates to GetUser.This change eliminates direct Locals access and ensures
HasUserbenefits fromGetUser's type-safe assertion logic.
94-100: LGTM! Improved nil safety with explicit pointer return.The signature change from
models.Userto*models.Userwith explicitnilreturn provides a clear contract: the caller must check fornilrather than relying on zero-value semantics. This prevents ambiguity between "no user" and "empty user."
118-123: LGTM! Critical nil check prevents unsafe dereference.The explicit nil check at lines 119-121 is essential defensive code that prevents a panic when dereferencing the user pointer at line 123. This properly handles the case where
GetUserreturnsniland provides a clear 401 response.internal/sms-gateway/modules/sse/service.go (5)
17-19: LGTM: Named constant improves maintainability.Extracting the buffer size into a named constant eliminates the magic number and provides a single source of truth.
54-105: LGTM: Improved error wrapping and log formatting.The changes enhance error handling by using
%wfor proper error wrapping withErrNoConnection, enablingerrors.Ischecks. The multiline log formatting also improves readability. The concurrency control withRLockis appropriate.
120-131: LGTM: Clean separation of concerns.Delegating the streaming logic to
handleStreamimproves code organization by separating HTTP setup from the streaming lifecycle. This enhances maintainability and testability.
166-174: LGTM: Correct keepalive error handling and metrics.The keepalive handling properly returns on write errors and increments the metric only after a successful write, ensuring accurate metrics.
133-179: LGTM: Proper lifecycle management and write error handling, with keepAlivePeriod validation confirmed.The
handleStreammethod correctly manages the connection lifecycle with deferred cleanup. The write error handling at lines 151-163 properly addresses the previous review concern—errors fromwriteToStreamnow causehandleStreamto exit, triggering the deferredremoveConnectionand keeping metrics accurate.The concern about negative
keepAlivePeriodvalues is also addressed:WithKeepAlivePeriod()in config.go validates negative durations and replaces them with the default value, preventing any panic whentime.NewTickeris called at line 141.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 0
🧹 Nitpick comments (1)
pkg/cache/memory_concurrency_test.go (1)
399-437: Consider using the test parameter for t.Parallel().While using
_for an unused parameter is valid, the test could benefit from callingt.Parallel()to run concurrently with other tests, potentially catching more race conditions. This would require keeping thetparameter.Apply this diff if you'd like to enable parallel execution:
-func TestMemoryCache_RaceConditionDetection(_ *testing.T) { +func TestMemoryCache_RaceConditionDetection(t *testing.T) { + t.Parallel() // This test is specifically designed to detect race conditions
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (2)
pkg/cache/memory_bench_test.go(19 hunks)pkg/cache/memory_concurrency_test.go(12 hunks)
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2025-09-10T11:11:43.462Z
Learnt from: capcom6
Repo: android-sms-gateway/server PR: 178
File: pkg/cache/memory.go:59-74
Timestamp: 2025-09-10T11:11:43.462Z
Learning: In pkg/cache/memory.go, the cleanup method removes expired items first, then executes the provided callback function. This means methods like Drain that use cleanup don't need additional expiration filtering since expired items are already removed before the callback runs.
Applied to files:
pkg/cache/memory_concurrency_test.gopkg/cache/memory_bench_test.go
🧬 Code graph analysis (2)
pkg/cache/memory_concurrency_test.go (1)
pkg/cache/errors.go (2)
ErrKeyNotFound(9-9)ErrKeyExists(13-13)
pkg/cache/memory_bench_test.go (1)
pkg/cache/memory.go (1)
NewMemory(16-23)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (4)
- GitHub Check: E2E
- GitHub Check: Lint
- GitHub Check: Benchmark
- GitHub Check: Analyze (go)
🔇 Additional comments (8)
pkg/cache/memory_bench_test.go (2)
14-14: LGTM! Documentation improvements.The addition of periods to benchmark function comments improves consistency with Go documentation conventions.
Also applies to: 29-29, 47-47, 66-66, 81-81, 100-100, 119-119, 139-139, 159-159, 192-192, 225-225, 267-267, 307-307, 339-339, 377-377, 404-404, 428-428, 446-446
125-125: LGTM! Modern range-over-integer syntax is fully supported.The range-over-integer loop modernization improves readability and is semantically equivalent to the traditional for loops. The project requires Go 1.24.1, which fully supports this syntax (introduced in Go 1.22). The changes are safe and consistent with current best practices.
pkg/cache/memory_concurrency_test.go (6)
5-5: LGTM! Import added for idiomatic error checking.The errors package import is correctly added to support errors.Is usage throughout the test file.
118-118: Excellent: Proper sentinel error checking with errors.Is.The migration from direct error comparison to errors.Is correctly handles potential error wrapping and follows Go best practices for sentinel error checking.
Also applies to: 224-224, 298-298, 345-345, 380-380, 393-393
221-228: LGTM! Idiomatic switch pattern for error checking.The switch statement correctly uses boolean cases with errors.Is, which is more idiomatic than switching on the error value directly when checking wrapped errors.
284-284: LGTM! Correct use of blank identifier for unused parameter.The key parameter is not needed in the Range callback, so using
_is the appropriate Go idiom.
37-40: LGTM! Improved variable naming prevents shadowing.Renaming to
getErravoids potential shadowing issues and makes the error handling clearer within the goroutine.
32-32: No issues found—range-based loops are fully supported.The project specifies Go 1.24.1 in go.mod, which is well above the Go 1.22 minimum required for range-based loop syntax. All loop conversions in the file are valid and compatible.
c710e34 to
ac843cb
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 2
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
pkg/health/types.go (1)
31-38: Don’t rely on Go 1.21-onlymaxhere
maxbecame a predeclared identifier only in Go 1.21. Unless this PR also bumps our toolchain (I didn’t spot that change), this will not compile because there’s no package-level helper. Please inline the comparisons so the code keeps building under the current Go version.- case StatusFail: - level = max(level, levelFail) - case StatusWarn: - level = max(level, levelWarn) + case StatusFail: + if level < levelFail { + level = levelFail + } + case StatusWarn: + if level < levelWarn { + level = levelWarn + }
♻️ Duplicate comments (5)
pkg/cache/options.go (1)
22-28: Negative TTLs need a past timestamp againWe just reintroduced the regression where
ttl < 0leavesvalidUntilset totime.Now(). The memory cache’s expiry check still usesnow.After(validUntil), so equality lets the entry survive until some later read instead of being immediately purged. Please put this branch back to producing a strictly past time.- case ttl < 0: - o.validUntil = time.Now() + case ttl < 0: + o.validUntil = time.Now().Add(ttl)internal/sms-gateway/models/models.go (1)
26-31: Fix typo in nolint comment.The nolint comment still says “pertial”; please correct it back to “partial” to match the prior fix.
- //nolint:exhaustruct // pertial constructor + //nolint:exhaustruct // partial constructorinternal/config/module.go (2)
26-37: Wrap private Config provider with fx.Annotate.Passing
fx.Privateas a separate argument makes Fx treat it like a constructor, causing startup errors. Wrap the provider withfx.Annotate(..., fx.Private)instead.Apply this diff:
fx.Provide( - func(log *zap.Logger) Config { + fx.Annotate( + func(log *zap.Logger) Config { defaultConfig := Default() if err := config.LoadConfig(&defaultConfig); err != nil { log.Error("Error loading config", zap.Error(err)) } return defaultConfig - }, - fx.Private, + }, + fx.Private, + ), ),
90-99: Don't override API path when only host is omitted.The current logic resets
cfg.HTTP.API.Pathto/apiwhenever the host is blank, overwriting any explicit path users configured. Check the path field itself instead.Apply this diff:
// Default and normalize API path/host - if cfg.HTTP.API.Host == "" { + if cfg.HTTP.API.Path == "" { cfg.HTTP.API.Path = "/api" }internal/sms-gateway/modules/auth/service.go (1)
55-57: Document why the nanoid error is safe to ignore.Prior feedback still applies—the returned error is silently dropped. Please either handle it or add the short justification inline so future readers know it’s intentional.
- idgen, _ := nanoid.Standard(idLen) + idgen, _ := nanoid.Standard(idLen) // size 21 is within nanoid.Standard constraints, so error is impossible
🧹 Nitpick comments (12)
Makefile (1)
10-10: Consolidate the two.PHONYdeclarations into a single declaration.Having two separate
.PHONYdeclarations is unconventional and can lead to maintenance confusion. While both are technically valid, it's clearer and more maintainable to have all targets in a single.PHONYdeclaration, with optional comments distinguishing public vs. private/development targets if needed.-.PHONY: all fmt lint test coverage benchmark deps release clean help +.PHONY: \ + all fmt lint test coverage benchmark deps release clean help \ + init init-dev ngrok air db-upgrade db-upgrade-raw run test-e2e build install \ + docker-build docker docker-dev docker-cleanAlso applies to: 88-88
pkg/cache/memory_concurrency_test.go (2)
221-228: Consider simplifying the error check pattern.The switch statement mixes a direct
nilcheck witherrors.Is(). An if-else chain would be more straightforward and consistent:- switch { - case err == nil: - successCount.Add(1) - case errors.Is(err, cache.ErrKeyExists): - existsCount.Add(1) - default: - t.Errorf("SetOrFail failed: %v", err) - } + if err == nil { + successCount.Add(1) + } else if errors.Is(err, cache.ErrKeyExists) { + existsCount.Add(1) + } else { + t.Errorf("SetOrFail failed: %v", err) + }
399-399: Consider keeping the test parameter for future extensibility.Using
_for the unused*testing.Tparameter is valid, but keeping the named parameter (even if unused) allows adding assertions later without changing the function signature.-func TestMemoryCache_RaceConditionDetection(_ *testing.T) { +func TestMemoryCache_RaceConditionDetection(t *testing.T) {Dockerfile.goreleaser (1)
1-1: Specify a pinned Alpine version for reproducible builds.Using
alpine:3without a patch version can lead to non-deterministic builds if the base image is updated upstream. Pin to a specific version (e.g.,alpine:3.20or latest 3.x LTS).-FROM alpine:3 +FROM alpine:3.20.github/workflows/release.yml (2)
63-64: Consider a more readable APP_VERSION extraction.The current extraction using
rev | cut | revworks correctly but is less readable than bash parameter expansion. For a tag ref likerefs/tags/v1.0.0, using${GITHUB_REF#refs/tags/}is more direct and easier to maintain.- run: echo APP_VERSION=$(echo ${GITHUB_REF} | rev | cut -d'/' -f 1 | rev ) >> ${GITHUB_ENV} + run: echo APP_VERSION=${GITHUB_REF#refs/tags/} >> ${GITHUB_ENV}
70-72: Consider updating Terraform version and action versions.Terraform 1.4.6 (released Mar 2023) is significantly outdated; the latest is 1.9+. While the workflow may have compatibility requirements, consider updating to a more recent stable version for security patches and features. Additionally,
checkout@v3andsetup-terraform@v2have newer versions available (v4andv3respectively).- name: Install Terraform uses: hashicorp/setup-terraform@v2 + # Consider: hashicorp/setup-terraform@v3 with: - terraform_version: 1.4.6 + terraform_version: 1.9.0internal/sms-gateway/pubsub/module.go (1)
5-5: Good error wrapping improvement.Adding the
fmtimport and wrapping the close error provides better error context for lifecycle shutdown failures.Optional: Consider using more formal error message style:
- return fmt.Errorf("can't close pubsub: %w", err) + return fmt.Errorf("failed to close pubsub: %w", err)Also applies to: 23-23
internal/sms-gateway/handlers/mobile.go (1)
275-277: Handle ID generator init failure explicitly.If
nanoid.Standardever returns an error (size out of range, RNG init fault, library change), we silently keep a nil generator and the firsth.idGen()call will panic. Please fail fast instead of discarding the error.const idGenSize = 21 -idGen, _ := nanoid.Standard(idGenSize) +idGen, err := nanoid.Standard(idGenSize) +if err != nil { + params.Logger.Fatal("failed to initialize ID generator", zap.Error(err)) +}internal/sms-gateway/modules/devices/service.go (1)
15-17: Note the unused constant.The
tokenCacheTTLconstant is defined but doesn't appear to be used in the visible code. Ensure it's referenced where needed for token cache expiration logic, or consider removing it if it's no longer required.internal/sms-gateway/modules/settings/models.go (1)
19-24: Consider copying the settings map defensively.If callers reuse or mutate the
settingsmap after invokingNewDeviceSettings, they’ll implicitly mutate the stored entity as well. A shallow copy would guard against accidental shared-state surprises.func NewDeviceSettings(userID string, settings map[string]any) *DeviceSettings { - //nolint:exhaustruct // partial constructor - return &DeviceSettings{ - UserID: userID, - Settings: settings, - } + //nolint:exhaustruct // partial constructor + cloned := maps.Clone(settings) // Go 1.21+ + return &DeviceSettings{ + UserID: userID, + Settings: cloned, + } }internal/sms-gateway/modules/push/fcm/client.go (1)
24-25: Consider removing redundant zero-value initializations.The explicit initialization of
clienttonilandmuxtosync.Mutex{}is unnecessary since these are the zero values for their respective types. The code works correctly, but removing these lines would be more idiomatic.return &Client{ options: options, - client: nil, - mux: sync.Mutex{}, }, nilinternal/config/module.go (1)
118-122: TODO: Make UnusedLifetime configurable.The TODO comment on line 120 indicates that the hardcoded 365-day unused device lifetime should be made configurable.
Would you like me to generate a solution that adds this configuration option to the config structure?
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (107)
.github/workflows/docker-build.yml(0 hunks).github/workflows/docker-publish.yml(0 hunks).github/workflows/go.yml(3 hunks).github/workflows/pr.yml(1 hunks).github/workflows/release.yml(2 hunks).gitignore(1 hunks).golangci.yml(1 hunks).goreleaser.yaml(1 hunks)Dockerfile.goreleaser(1 hunks)Makefile(2 hunks)internal/config/config.go(3 hunks)internal/config/module.go(1 hunks)internal/sms-gateway/app.go(2 hunks)internal/sms-gateway/cache/errors.go(1 hunks)internal/sms-gateway/cache/factory.go(2 hunks)internal/sms-gateway/handlers/base/handler_test.go(5 hunks)internal/sms-gateway/handlers/converters/messages.go(1 hunks)internal/sms-gateway/handlers/devices/3rdparty.go(3 hunks)internal/sms-gateway/handlers/events/mobile.go(1 hunks)internal/sms-gateway/handlers/health.go(2 hunks)internal/sms-gateway/handlers/logs/3rdparty.go(2 hunks)internal/sms-gateway/handlers/messages/3rdparty.go(8 hunks)internal/sms-gateway/handlers/messages/mobile.go(2 hunks)internal/sms-gateway/handlers/messages/params.go(3 hunks)internal/sms-gateway/handlers/middlewares/userauth/userauth.go(2 hunks)internal/sms-gateway/handlers/mobile.go(8 hunks)internal/sms-gateway/handlers/module.go(1 hunks)internal/sms-gateway/handlers/root.go(1 hunks)internal/sms-gateway/handlers/settings/3rdparty.go(4 hunks)internal/sms-gateway/handlers/settings/mobile.go(2 hunks)internal/sms-gateway/handlers/upstream.go(3 hunks)internal/sms-gateway/handlers/webhooks/3rdparty.go(4 hunks)internal/sms-gateway/handlers/webhooks/mobile.go(2 hunks)internal/sms-gateway/models/migration.go(2 hunks)internal/sms-gateway/models/models.go(2 hunks)internal/sms-gateway/models/module.go(1 hunks)internal/sms-gateway/modules/auth/errors.go(1 hunks)internal/sms-gateway/modules/auth/module.go(1 hunks)internal/sms-gateway/modules/auth/repository.go(1 hunks)internal/sms-gateway/modules/auth/service.go(6 hunks)internal/sms-gateway/modules/auth/types.go(1 hunks)internal/sms-gateway/modules/db/health.go(3 hunks)internal/sms-gateway/modules/db/module.go(2 hunks)internal/sms-gateway/modules/devices/repository.go(4 hunks)internal/sms-gateway/modules/devices/repository_filter.go(1 hunks)internal/sms-gateway/modules/devices/service.go(3 hunks)internal/sms-gateway/modules/events/errors.go(1 hunks)internal/sms-gateway/modules/events/metrics.go(3 hunks)internal/sms-gateway/modules/events/module.go(1 hunks)internal/sms-gateway/modules/events/service.go(6 hunks)internal/sms-gateway/modules/events/types.go(2 hunks)internal/sms-gateway/modules/messages/cache.go(2 hunks)internal/sms-gateway/modules/messages/converters.go(1 hunks)internal/sms-gateway/modules/messages/errors.go(1 hunks)internal/sms-gateway/modules/messages/models.go(6 hunks)internal/sms-gateway/modules/messages/module.go(1 hunks)internal/sms-gateway/modules/messages/repository.go(5 hunks)internal/sms-gateway/modules/messages/repository_filter.go(2 hunks)internal/sms-gateway/modules/messages/service.go(10 hunks)internal/sms-gateway/modules/messages/service_test.go(0 hunks)internal/sms-gateway/modules/metrics/handler.go(1 hunks)internal/sms-gateway/modules/metrics/module.go(1 hunks)internal/sms-gateway/modules/push/client.go(1 hunks)internal/sms-gateway/modules/push/client/types.go(1 hunks)internal/sms-gateway/modules/push/fcm/client.go(4 hunks)internal/sms-gateway/modules/push/fcm/errors.go(1 hunks)internal/sms-gateway/modules/push/fcm/utils.go(1 hunks)internal/sms-gateway/modules/push/module.go(2 hunks)internal/sms-gateway/modules/push/service.go(11 hunks)internal/sms-gateway/modules/push/types.go(3 hunks)internal/sms-gateway/modules/push/upstream/client.go(5 hunks)internal/sms-gateway/modules/settings/models.go(1 hunks)internal/sms-gateway/modules/settings/module.go(1 hunks)internal/sms-gateway/modules/settings/repository.go(5 hunks)internal/sms-gateway/modules/settings/service.go(2 hunks)internal/sms-gateway/modules/settings/utils.go(4 hunks)internal/sms-gateway/modules/sse/config.go(2 hunks)internal/sms-gateway/modules/sse/errors.go(1 hunks)internal/sms-gateway/modules/sse/metrics.go(3 hunks)internal/sms-gateway/modules/sse/module.go(1 hunks)internal/sms-gateway/modules/sse/service.go(8 hunks)internal/sms-gateway/modules/webhooks/errors.go(3 hunks)internal/sms-gateway/modules/webhooks/models.go(1 hunks)internal/sms-gateway/modules/webhooks/module.go(1 hunks)internal/sms-gateway/modules/webhooks/repository.go(2 hunks)internal/sms-gateway/modules/webhooks/repository_filter.go(1 hunks)internal/sms-gateway/modules/webhooks/service.go(2 hunks)internal/sms-gateway/openapi/docs.go(5 hunks)internal/sms-gateway/pubsub/module.go(2 hunks)internal/sms-gateway/pubsub/pubsub.go(3 hunks)internal/worker/executor/service.go(1 hunks)pkg/cache/errors.go(1 hunks)pkg/cache/memory.go(12 hunks)pkg/cache/memory_bench_test.go(19 hunks)pkg/cache/memory_concurrency_test.go(12 hunks)pkg/cache/memory_edge_test.go(8 hunks)pkg/cache/memory_profile_test.go(2 hunks)pkg/cache/memory_test.go(8 hunks)pkg/cache/options.go(1 hunks)pkg/cache/redis.go(15 hunks)pkg/health/health.go(2 hunks)pkg/health/module.go(2 hunks)pkg/health/service.go(2 hunks)pkg/health/types.go(3 hunks)pkg/pubsub/memory.go(8 hunks)pkg/pubsub/options.go(1 hunks)pkg/pubsub/pubsub.go(1 hunks)
⛔ Files not processed due to max files limit (3)
- pkg/pubsub/redis.go
- test/e2e/mobile_test.go
- test/e2e/utils_test.go
💤 Files with no reviewable changes (3)
- internal/sms-gateway/modules/messages/service_test.go
- .github/workflows/docker-build.yml
- .github/workflows/docker-publish.yml
🚧 Files skipped from review as they are similar to previous changes (28)
- internal/sms-gateway/handlers/events/mobile.go
- pkg/cache/errors.go
- internal/sms-gateway/modules/auth/errors.go
- internal/sms-gateway/modules/events/errors.go
- internal/sms-gateway/handlers/settings/mobile.go
- internal/sms-gateway/modules/sse/module.go
- pkg/cache/memory_profile_test.go
- internal/sms-gateway/handlers/converters/messages.go
- internal/sms-gateway/modules/webhooks/repository.go
- internal/sms-gateway/modules/push/client.go
- internal/sms-gateway/modules/events/types.go
- internal/sms-gateway/modules/messages/converters.go
- pkg/cache/memory_edge_test.go
- internal/sms-gateway/cache/errors.go
- internal/sms-gateway/modules/devices/repository.go
- internal/sms-gateway/modules/webhooks/repository_filter.go
- internal/sms-gateway/modules/db/module.go
- .github/workflows/pr.yml
- internal/sms-gateway/modules/messages/cache.go
- internal/sms-gateway/modules/sse/config.go
- internal/sms-gateway/modules/webhooks/errors.go
- pkg/cache/memory_bench_test.go
- .github/workflows/go.yml
- internal/sms-gateway/pubsub/pubsub.go
- .golangci.yml
- internal/sms-gateway/handlers/upstream.go
- internal/config/config.go
- internal/sms-gateway/modules/push/fcm/errors.go
🧰 Additional context used
🧠 Learnings (7)
📚 Learning: 2025-09-10T23:45:45.502Z
Learnt from: capcom6
Repo: android-sms-gateway/server PR: 178
File: internal/sms-gateway/online/module.go:14-16
Timestamp: 2025-09-10T23:45:45.502Z
Learning: fx.Decorate in uber-go/fx creates module-scoped decorated dependencies, not global modifications. Using fx.Decorate(func(log *zap.Logger) *zap.Logger { return log.Named("module_name") }) is a correct pattern for creating module-specific loggers that are scoped only to that module's dependency graph.
Applied to files:
internal/sms-gateway/modules/events/module.gointernal/sms-gateway/modules/metrics/module.gointernal/sms-gateway/modules/webhooks/module.gointernal/sms-gateway/pubsub/module.gointernal/config/module.gointernal/sms-gateway/handlers/module.gointernal/sms-gateway/modules/settings/module.gointernal/sms-gateway/modules/auth/module.go
📚 Learning: 2025-09-10T11:11:43.462Z
Learnt from: capcom6
Repo: android-sms-gateway/server PR: 178
File: pkg/cache/memory.go:59-74
Timestamp: 2025-09-10T11:11:43.462Z
Learning: In pkg/cache/memory.go, the cleanup method removes expired items first, then executes the provided callback function. This means methods like Drain that use cleanup don't need additional expiration filtering since expired items are already removed before the callback runs.
Applied to files:
pkg/cache/memory_test.gopkg/cache/memory_concurrency_test.gopkg/cache/redis.gopkg/cache/memory.go
📚 Learning: 2025-09-05T08:37:28.077Z
Learnt from: eznix86
Repo: android-sms-gateway/server PR: 172
File: deployments/kubernetes/sms-gateway-server/Chart.yaml:9-10
Timestamp: 2025-09-05T08:37:28.077Z
Learning: In the android-sms-gateway/server project, the Helm chart's appVersion in Chart.yaml is set to "latest" as a placeholder because CI automatically updates it with the actual version from git tags during the release process via the bump-chart-app-version.yaml workflow.
Applied to files:
.goreleaser.yaml
📚 Learning: 2025-09-10T23:53:42.006Z
Learnt from: capcom6
Repo: android-sms-gateway/server PR: 178
File: pkg/cache/redis.go:63-75
Timestamp: 2025-09-10T23:53:42.006Z
Learning: In pkg/cache/redis.go, the Redis cache implementation uses Redis's native field-level TTL via HExpireAt, meaning expired fields are automatically removed by Redis itself. This differs from the memory implementation where the application tracks expiration times. As a result, operations like Drain that call HGetAll will only return non-expired items since expired fields are already removed by Redis.
Applied to files:
pkg/cache/options.gopkg/cache/redis.gopkg/cache/memory.go
📚 Learning: 2025-07-22T00:34:31.896Z
Learnt from: capcom6
Repo: android-sms-gateway/server PR: 154
File: internal/sms-gateway/modules/sse/service.go:89-92
Timestamp: 2025-07-22T00:34:31.896Z
Learning: In Go SSE implementations, when cleaning up connections with buffered channels, avoid closing the data channels directly as this can cause panics if concurrent goroutines attempt to write to them. Instead, use a separate close signal channel to notify writers that the connection should be terminated, allowing the data channels to be garbage collected naturally when the connection is removed.
Applied to files:
internal/sms-gateway/modules/sse/errors.gointernal/sms-gateway/modules/sse/service.go
📚 Learning: 2025-07-22T00:31:28.796Z
Learnt from: capcom6
Repo: android-sms-gateway/server PR: 154
File: internal/sms-gateway/modules/sse/service.go:67-79
Timestamp: 2025-07-22T00:31:28.796Z
Learning: In the SSE service Send method (internal/sms-gateway/modules/sse/service.go), blocking on full channels is intentional and acceptable. Timeout control is handled at the calling code level through context, not within the Send method itself. The user prefers this approach over adding a default case that would drop messages when channels are full.
Applied to files:
internal/sms-gateway/modules/events/service.gointernal/sms-gateway/modules/sse/service.go
📚 Learning: 2025-09-10T23:54:16.828Z
Learnt from: capcom6
Repo: android-sms-gateway/server PR: 178
File: pkg/cache/redis.go:118-121
Timestamp: 2025-09-10T23:54:16.828Z
Learning: Redis 7.4.0 introduced HEXPIRE, HEXPIREAT, and related commands that support per-field TTL on hash data structures. go-redis v9.6.0+ includes support for these commands with methods like HExpireAt.
Applied to files:
pkg/cache/redis.go
🧬 Code graph analysis (49)
internal/sms-gateway/modules/settings/service.go (1)
internal/sms-gateway/modules/settings/models.go (1)
NewDeviceSettings(19-25)
internal/sms-gateway/modules/events/module.go (2)
internal/sms-gateway/app.go (1)
Module(33-58)internal/sms-gateway/modules/events/service.go (2)
NewService(33-52)Service(20-31)
pkg/cache/memory_test.go (1)
pkg/cache/errors.go (2)
ErrKeyNotFound(9-9)ErrKeyExists(13-13)
internal/sms-gateway/modules/metrics/module.go (1)
internal/sms-gateway/app.go (1)
Module(33-58)
internal/sms-gateway/modules/webhooks/module.go (2)
internal/sms-gateway/app.go (1)
Module(33-58)internal/sms-gateway/handlers/module.go (1)
Module(15-41)
internal/sms-gateway/modules/webhooks/service.go (1)
internal/sms-gateway/modules/webhooks/errors.go (1)
ErrInvalidEvent(9-9)
pkg/health/module.go (1)
pkg/health/types.go (1)
Provider(69-75)
internal/sms-gateway/handlers/webhooks/mobile.go (1)
internal/sms-gateway/modules/webhooks/service.go (1)
Service(28-37)
internal/sms-gateway/handlers/middlewares/userauth/userauth.go (1)
internal/sms-gateway/models/models.go (1)
User(18-24)
internal/sms-gateway/handlers/mobile.go (1)
internal/sms-gateway/models/models.go (2)
Device(34-45)User(18-24)
internal/sms-gateway/models/migration.go (1)
internal/sms-gateway/models/models.go (2)
User(18-24)Device(34-45)
internal/sms-gateway/modules/push/fcm/client.go (2)
internal/sms-gateway/modules/push/fcm/errors.go (1)
ErrInitializationFailed(6-6)internal/sms-gateway/modules/push/upstream/client.go (1)
Client(22-27)
internal/sms-gateway/modules/webhooks/models.go (2)
internal/sms-gateway/models/models.go (3)
SoftDeletableModel(12-16)User(18-24)Device(34-45)internal/sms-gateway/modules/settings/models.go (1)
Migrate(27-32)
internal/sms-gateway/modules/devices/service.go (3)
internal/sms-gateway/models/models.go (1)
Device(34-45)internal/sms-gateway/modules/devices/repository_filter.go (4)
SelectFilter(9-9)WithUserID(23-27)WithID(11-15)ActiveWithin(29-33)internal/sms-gateway/modules/devices/repository.go (1)
ErrNotFound(14-14)
internal/config/module.go (7)
internal/sms-gateway/app.go (1)
Module(33-58)internal/sms-gateway/modules/sse/config.go (4)
Option(5-5)Config(7-9)NewConfig(19-27)WithKeepAlivePeriod(33-41)internal/config/config.go (13)
Config(10-20)Default(88-125)HTTP(27-33)Database(44-55)Gateway(22-25)API(35-38)GatewayModePublic(6-6)OpenAPI(40-42)Messages(75-78)Tasks(63-65)SSE(71-73)Cache(80-82)PubSub(84-86)internal/sms-gateway/modules/auth/service.go (1)
Config(22-25)internal/sms-gateway/modules/push/service.go (1)
Config(23-30)internal/sms-gateway/modules/messages/config.go (1)
Config(5-8)internal/sms-gateway/handlers/config.go (1)
Config(3-11)
pkg/pubsub/memory.go (2)
pkg/pubsub/options.go (1)
Option(3-3)pkg/pubsub/pubsub.go (2)
Subscription(19-24)PubSub(46-51)
internal/sms-gateway/handlers/health.go (1)
pkg/health/types.go (1)
CheckDetail(54-64)
internal/sms-gateway/handlers/messages/mobile.go (2)
internal/sms-gateway/handlers/base/handler.go (1)
Handler(15-18)internal/sms-gateway/models/models.go (1)
Device(34-45)
internal/sms-gateway/handlers/module.go (1)
internal/sms-gateway/app.go (1)
Module(33-58)
pkg/cache/memory_concurrency_test.go (1)
pkg/cache/errors.go (2)
ErrKeyNotFound(9-9)ErrKeyExists(13-13)
pkg/health/service.go (1)
pkg/health/types.go (3)
Provider(69-75)Checks(67-67)CheckResult(21-24)
internal/sms-gateway/handlers/messages/params.go (1)
internal/sms-gateway/modules/messages/repository_filter.go (3)
SelectFilter(16-23)SelectOptions(51-62)Order(7-7)
internal/sms-gateway/handlers/messages/3rdparty.go (6)
internal/sms-gateway/handlers/devices/3rdparty.go (2)
NewThirdPartyController(24-36)ThirdPartyController(18-22)internal/sms-gateway/handlers/logs/3rdparty.go (2)
NewThirdPartyController(24-31)ThirdPartyController(20-22)internal/sms-gateway/handlers/webhooks/3rdparty.go (2)
NewThirdPartyController(32-40)ThirdPartyController(26-30)internal/sms-gateway/handlers/base/handler.go (1)
Handler(15-18)internal/sms-gateway/modules/devices/repository.go (1)
ErrNotFound(14-14)internal/sms-gateway/modules/messages/service.go (1)
EnqueueOptions(26-28)
internal/sms-gateway/modules/messages/models.go (1)
internal/sms-gateway/models/models.go (1)
SoftDeletableModel(12-16)
internal/sms-gateway/modules/auth/repository.go (1)
internal/sms-gateway/models/models.go (1)
User(18-24)
internal/sms-gateway/modules/events/service.go (7)
internal/sms-gateway/modules/sse/service.go (2)
NewService(42-52)Service(21-29)internal/sms-gateway/modules/push/service.go (1)
Service(32-41)internal/sms-gateway/pubsub/pubsub.go (1)
PubSub(15-15)internal/sms-gateway/modules/events/errors.go (1)
ErrValidationFailed(6-6)internal/sms-gateway/modules/events/metrics.go (6)
DeliveryTypeUnknown(20-20)FailureReasonPublishError(23-23)EventTypeUnknown(26-26)FailureReasonSerializationError(22-22)DeliveryTypePush(18-18)FailureReasonProviderFailed(24-24)internal/sms-gateway/modules/events/types.go (1)
Event(10-13)internal/sms-gateway/modules/sse/types.go (1)
Event(7-10)
internal/sms-gateway/modules/push/module.go (3)
internal/sms-gateway/modules/push/fcm/client.go (2)
New(21-27)Client(14-19)internal/sms-gateway/modules/push/service.go (1)
New(43-73)internal/sms-gateway/modules/push/upstream/client.go (2)
New(29-35)Client(22-27)
internal/sms-gateway/handlers/logs/3rdparty.go (3)
internal/sms-gateway/handlers/devices/3rdparty.go (2)
NewThirdPartyController(24-36)ThirdPartyController(18-22)internal/sms-gateway/handlers/messages/3rdparty.go (2)
NewThirdPartyController(44-53)ThirdPartyController(37-42)internal/sms-gateway/handlers/webhooks/3rdparty.go (2)
NewThirdPartyController(32-40)ThirdPartyController(26-30)
internal/sms-gateway/modules/settings/repository.go (1)
internal/sms-gateway/modules/settings/models.go (1)
DeviceSettings(10-17)
internal/sms-gateway/handlers/devices/3rdparty.go (4)
internal/sms-gateway/modules/devices/service.go (1)
Service(19-28)internal/sms-gateway/handlers/logs/3rdparty.go (2)
NewThirdPartyController(24-31)ThirdPartyController(20-22)internal/sms-gateway/handlers/messages/3rdparty.go (2)
NewThirdPartyController(44-53)ThirdPartyController(37-42)internal/sms-gateway/handlers/webhooks/3rdparty.go (2)
NewThirdPartyController(32-40)ThirdPartyController(26-30)
internal/sms-gateway/modules/settings/models.go (2)
internal/sms-gateway/models/models.go (2)
TimedModel(7-10)User(18-24)internal/sms-gateway/modules/webhooks/models.go (1)
Migrate(38-43)
internal/sms-gateway/modules/sse/service.go (2)
internal/sms-gateway/modules/sse/errors.go (1)
ErrNoConnection(6-6)internal/sms-gateway/modules/sse/metrics.go (1)
ErrorTypeWriteFailure(21-21)
internal/sms-gateway/modules/push/service.go (3)
internal/sms-gateway/modules/push/fcm/client.go (1)
Client(14-19)internal/sms-gateway/modules/push/upstream/client.go (1)
Client(22-27)internal/sms-gateway/modules/push/types.go (1)
Event(17-17)
internal/sms-gateway/modules/settings/module.go (3)
internal/sms-gateway/app.go (1)
Module(33-58)internal/sms-gateway/handlers/module.go (1)
Module(15-41)internal/sms-gateway/modules/settings/service.go (1)
NewService(27-35)
internal/sms-gateway/modules/auth/service.go (5)
internal/sms-gateway/online/service.go (2)
New(28-37)Service(14-17)internal/sms-gateway/models/models.go (4)
User(18-24)NewUser(26-32)Device(34-45)NewDevice(47-53)internal/sms-gateway/modules/auth/types.go (1)
OneTimeCode(15-18)pkg/crypto/passwords.go (2)
MakeBCryptHash(14-20)CompareBCryptHash(22-27)internal/sms-gateway/modules/auth/errors.go (1)
ErrAuthorizationFailed(6-6)
internal/sms-gateway/modules/auth/module.go (1)
internal/sms-gateway/app.go (1)
Module(33-58)
internal/sms-gateway/cache/factory.go (1)
pkg/cache/errors.go (1)
ErrInvalidConfig(7-7)
pkg/cache/redis.go (3)
pkg/cache/errors.go (1)
ErrInvalidConfig(7-7)pkg/cache/options.go (3)
GetOption(49-49)AndDelete(89-93)Option(6-6)pkg/cache/cache.go (1)
Cache(5-40)
internal/sms-gateway/handlers/settings/3rdparty.go (2)
internal/sms-gateway/models/models.go (1)
User(18-24)internal/sms-gateway/modules/settings/models.go (1)
DeviceSettings(10-17)
internal/sms-gateway/modules/messages/repository.go (2)
internal/sms-gateway/modules/messages/repository_filter.go (3)
SelectFilter(16-23)SelectOptions(51-62)Order(7-7)internal/sms-gateway/modules/messages/models.go (2)
Message(35-55)ProcessingStatePending(16-16)
internal/sms-gateway/modules/messages/repository_filter.go (2)
internal/sms-gateway/modules/webhooks/repository_filter.go (2)
WithExtID(7-11)WithDeviceID(41-46)internal/sms-gateway/modules/messages/models.go (1)
ProcessingState(12-12)
internal/sms-gateway/modules/messages/service.go (4)
internal/sms-gateway/modules/messages/repository_filter.go (3)
Order(7-7)SelectFilter(16-23)SelectOptions(51-62)internal/sms-gateway/modules/messages/models.go (3)
MessageState(129-134)ProcessingState(12-12)MessageRecipient(111-117)internal/sms-gateway/modules/messages/repository.go (1)
ErrMessageNotFound(16-16)internal/sms-gateway/modules/messages/errors.go (1)
ValidationError(9-9)
pkg/cache/memory.go (2)
pkg/cache/options.go (3)
GetOption(49-49)AndDelete(89-93)Option(6-6)pkg/cache/cache.go (1)
Cache(5-40)
internal/sms-gateway/modules/db/health.go (1)
pkg/health/types.go (2)
Checks(67-67)Provider(69-75)
internal/sms-gateway/modules/push/upstream/client.go (1)
internal/sms-gateway/modules/push/fcm/client.go (2)
New(21-27)Client(14-19)
pkg/health/health.go (1)
pkg/health/types.go (6)
Checks(67-67)CheckDetail(54-64)Status(7-7)StatusPass(11-11)StatusWarn(12-12)Provider(69-75)
internal/sms-gateway/handlers/webhooks/3rdparty.go (4)
internal/sms-gateway/handlers/logs/3rdparty.go (2)
NewThirdPartyController(24-31)ThirdPartyController(20-22)internal/sms-gateway/handlers/messages/3rdparty.go (2)
NewThirdPartyController(44-53)ThirdPartyController(37-42)internal/sms-gateway/models/models.go (1)
User(18-24)internal/sms-gateway/modules/webhooks/models.go (1)
Webhook(11-25)
internal/sms-gateway/modules/push/fcm/utils.go (1)
internal/sms-gateway/modules/push/types.go (1)
Event(17-17)
internal/sms-gateway/modules/push/types.go (2)
internal/sms-gateway/modules/events/types.go (1)
Event(10-13)internal/sms-gateway/modules/sse/types.go (1)
Event(7-10)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (4)
- GitHub Check: E2E
- GitHub Check: Benchmark
- GitHub Check: Test
- GitHub Check: Analyze (go)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 5
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
internal/sms-gateway/modules/push/fcm/client.go (1)
82-86: Race condition: Close lacks mutex protection.The
Closemethod setsc.client = nilwithout acquiring the mutex, whileOpen(lines 29-35) andSendboth accessc.clientunder mutex protection. Concurrent calls toCloseandOpen/Sendcan cause a data race.Apply this diff to protect the field access:
func (c *Client) Close(_ context.Context) error { + c.mux.Lock() + defer c.mux.Unlock() + c.client = nil return nil }
♻️ Duplicate comments (3)
internal/sms-gateway/handlers/mobile.go (1)
275-277: Handle the nanoid generator errorLine 276 still ignores the potential error from
nanoid.Standard. This was called out previously and remains unresolved, so we still risk starting the handler with a nil generator if the call ever fails.- const idGenSize = 21 - idGen, _ := nanoid.Standard(idGenSize) + const idGenSize = 21 + idGen, err := nanoid.Standard(idGenSize) + if err != nil { + params.Logger.Fatal("failed to initialize ID generator", zap.Error(err)) + }internal/sms-gateway/modules/auth/service.go (2)
55-56: Document the ignored error.The error from
nanoid.Standard(idLen)is ignored. While21is a valid size, add a comment explaining why it's safe to ignore.Apply this diff:
const idLen = 21 + // nanoid.Standard with size 21 never returns an error idgen, _ := nanoid.Standard(idLen)
218-219: Fix cache invalidation key to match AuthorizeUser format.The cache key construction here doesn't match the format used in
AuthorizeUser(Line 160).AuthorizeUserusesusername + "\x00" + password, but this code usesuserID + currentPasswordwithout the separator. This mismatch will cause cache invalidation to fail.Apply this diff:
// Invalidate cache - hash := sha256.Sum256([]byte(userID + currentPassword)) + hash := sha256.Sum256([]byte(userID + "\x00" + currentPassword)) cacheKey := hex.EncodeToString(hash[:])
🧹 Nitpick comments (5)
internal/sms-gateway/online/service.go (1)
85-85: Error message standardization looks good, but incomplete in this file.The updated error messages align with the repository-wide standardization to use "failed to..." instead of "can't...". However, other error messages in this same file remain inconsistent:
- Line 50: "Can't persist online status"
- Line 65: "Can't set online status"
- Line 101: "Can't parse last seen"
Consider updating these for consistency as well.
Also applies to: 111-111
pkg/pubsub/redis.go (1)
42-78: Good error wrapping; minor redundancy in initialization.The error wrapping with
ErrInvalidConfigis consistent with the pattern inpkg/cache/redis.go.Lines 73-74: The explicit zero-value initialization (
sync.WaitGroup{},sync.Mutex{}) is redundant since Go's zero values work correctly for these types.Optional cleanup:
- wg: sync.WaitGroup{}, - mu: sync.Mutex{}, + // wg and mu use zero values.github/workflows/release.yml (1)
56-56: Add explicit workflow failure notifications.Both
deployanddeploy-secondaryjobs depend ongoreleasercompleting successfully, but there is no explicit error handling if the goreleaser step fails (e.g., if artifacts are not published or docker images are not pushed). Consider:
- Adding workflow run status notifications (Slack, email, GitHub notifications)
- Adding conditional guards or post-job steps to ensure deployment is skipped if artifacts are missing
Also applies to: 99-99
internal/sms-gateway/modules/push/upstream/client.go (1)
29-35: Remove redundant zero-value initializations.Lines 32-33 explicitly initialize fields to their zero values (
nilfor pointer, zero value for struct). Go automatically zero-initializes all fields, making these assignments redundant.Apply this diff:
func New(options map[string]string) (*Client, error) { return &Client{ options: options, - client: nil, - mux: sync.Mutex{}, }, nil }internal/sms-gateway/modules/push/fcm/client.go (1)
21-27: Remove redundant zero-value initializations.Lines 24-25 explicitly initialize fields to their zero values. These assignments are redundant since Go automatically zero-initializes all fields.
Apply this diff:
func New(options map[string]string) (*Client, error) { return &Client{ options: options, - client: nil, - mux: sync.Mutex{}, }, nil }
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (36)
.github/workflows/release.yml(2 hunks)Dockerfile.goreleaser(1 hunks)Makefile(1 hunks)internal/sms-gateway/cache/factory.go(2 hunks)internal/sms-gateway/handlers/devices/3rdparty.go(4 hunks)internal/sms-gateway/handlers/messages/3rdparty.go(8 hunks)internal/sms-gateway/handlers/messages/mobile.go(2 hunks)internal/sms-gateway/handlers/mobile.go(8 hunks)internal/sms-gateway/handlers/settings/3rdparty.go(5 hunks)internal/sms-gateway/handlers/settings/mobile.go(2 hunks)internal/sms-gateway/handlers/webhooks/3rdparty.go(4 hunks)internal/sms-gateway/handlers/webhooks/mobile.go(2 hunks)internal/sms-gateway/modules/auth/service.go(6 hunks)internal/sms-gateway/modules/devices/service.go(6 hunks)internal/sms-gateway/modules/events/service.go(7 hunks)internal/sms-gateway/modules/events/types.go(2 hunks)internal/sms-gateway/modules/messages/cache.go(2 hunks)internal/sms-gateway/modules/messages/converters.go(1 hunks)internal/sms-gateway/modules/messages/repository.go(5 hunks)internal/sms-gateway/modules/messages/service.go(11 hunks)internal/sms-gateway/modules/push/fcm/client.go(4 hunks)internal/sms-gateway/modules/push/fcm/utils.go(1 hunks)internal/sms-gateway/modules/push/service.go(10 hunks)internal/sms-gateway/modules/push/types.go(3 hunks)internal/sms-gateway/modules/push/upstream/client.go(4 hunks)internal/sms-gateway/modules/settings/service.go(3 hunks)internal/sms-gateway/modules/sse/service.go(8 hunks)internal/sms-gateway/modules/webhooks/service.go(5 hunks)internal/sms-gateway/online/service.go(2 hunks)internal/sms-gateway/pubsub/module.go(2 hunks)internal/sms-gateway/pubsub/pubsub.go(2 hunks)internal/worker/config/types.go(2 hunks)internal/worker/executor/service.go(2 hunks)pkg/cache/redis.go(12 hunks)pkg/crypto/passwords.go(1 hunks)pkg/pubsub/redis.go(7 hunks)
✅ Files skipped from review due to trivial changes (2)
- internal/worker/config/types.go
- pkg/crypto/passwords.go
🚧 Files skipped from review as they are similar to previous changes (10)
- Dockerfile.goreleaser
- internal/sms-gateway/modules/settings/service.go
- internal/sms-gateway/modules/devices/service.go
- internal/sms-gateway/handlers/messages/mobile.go
- internal/sms-gateway/modules/events/service.go
- internal/worker/executor/service.go
- internal/sms-gateway/modules/events/types.go
- internal/sms-gateway/pubsub/module.go
- internal/sms-gateway/cache/factory.go
- internal/sms-gateway/modules/webhooks/service.go
🧰 Additional context used
🧠 Learnings (5)
📚 Learning: 2025-09-10T11:11:43.462Z
Learnt from: capcom6
Repo: android-sms-gateway/server PR: 178
File: pkg/cache/memory.go:59-74
Timestamp: 2025-09-10T11:11:43.462Z
Learning: In pkg/cache/memory.go, the cleanup method removes expired items first, then executes the provided callback function. This means methods like Drain that use cleanup don't need additional expiration filtering since expired items are already removed before the callback runs.
Applied to files:
internal/sms-gateway/online/service.gopkg/cache/redis.go
📚 Learning: 2025-09-10T23:53:42.006Z
Learnt from: capcom6
Repo: android-sms-gateway/server PR: 178
File: pkg/cache/redis.go:63-75
Timestamp: 2025-09-10T23:53:42.006Z
Learning: In pkg/cache/redis.go, the Redis cache implementation uses Redis's native field-level TTL via HExpireAt, meaning expired fields are automatically removed by Redis itself. This differs from the memory implementation where the application tracks expiration times. As a result, operations like Drain that call HGetAll will only return non-expired items since expired fields are already removed by Redis.
Applied to files:
internal/sms-gateway/online/service.gopkg/cache/redis.go
📚 Learning: 2025-07-22T00:31:28.796Z
Learnt from: capcom6
Repo: android-sms-gateway/server PR: 154
File: internal/sms-gateway/modules/sse/service.go:67-79
Timestamp: 2025-07-22T00:31:28.796Z
Learning: In the SSE service Send method (internal/sms-gateway/modules/sse/service.go), blocking on full channels is intentional and acceptable. Timeout control is handled at the calling code level through context, not within the Send method itself. The user prefers this approach over adding a default case that would drop messages when channels are full.
Applied to files:
internal/sms-gateway/modules/sse/service.go
📚 Learning: 2025-07-22T00:34:31.896Z
Learnt from: capcom6
Repo: android-sms-gateway/server PR: 154
File: internal/sms-gateway/modules/sse/service.go:89-92
Timestamp: 2025-07-22T00:34:31.896Z
Learning: In Go SSE implementations, when cleaning up connections with buffered channels, avoid closing the data channels directly as this can cause panics if concurrent goroutines attempt to write to them. Instead, use a separate close signal channel to notify writers that the connection should be terminated, allowing the data channels to be garbage collected naturally when the connection is removed.
Applied to files:
internal/sms-gateway/modules/sse/service.go
📚 Learning: 2025-09-10T23:54:16.828Z
Learnt from: capcom6
Repo: android-sms-gateway/server PR: 178
File: pkg/cache/redis.go:118-121
Timestamp: 2025-09-10T23:54:16.828Z
Learning: Redis 7.4.0 introduced HEXPIRE, HEXPIREAT, and related commands that support per-field TTL on hash data structures. go-redis v9.6.0+ includes support for these commands with methods like HExpireAt.
Applied to files:
pkg/cache/redis.go
🧬 Code graph analysis (21)
internal/sms-gateway/modules/messages/converters.go (1)
internal/sms-gateway/modules/messages/domain.go (1)
MessageOut(25-29)
internal/sms-gateway/modules/sse/service.go (2)
internal/sms-gateway/modules/sse/errors.go (1)
ErrNoConnection(6-6)internal/sms-gateway/modules/sse/metrics.go (2)
ErrorTypeMarshalError(22-22)ErrorTypeWriteFailure(21-21)
internal/sms-gateway/modules/push/types.go (2)
internal/sms-gateway/modules/events/types.go (1)
Event(10-13)internal/sms-gateway/modules/sse/types.go (1)
Event(7-10)
internal/sms-gateway/handlers/settings/mobile.go (4)
internal/sms-gateway/modules/settings/service.go (1)
Service(19-25)internal/sms-gateway/handlers/messages/mobile.go (2)
NewMobileController(35-43)MobileController(29-33)internal/sms-gateway/handlers/webhooks/mobile.go (2)
NewMobileController(21-33)MobileController(15-19)internal/sms-gateway/models/models.go (1)
Device(34-45)
internal/sms-gateway/handlers/webhooks/mobile.go (5)
internal/sms-gateway/modules/webhooks/service.go (1)
Service(28-37)internal/sms-gateway/handlers/messages/mobile.go (2)
NewMobileController(35-43)MobileController(29-33)internal/sms-gateway/handlers/settings/mobile.go (2)
NewMobileController(23-37)MobileController(16-21)internal/sms-gateway/models/models.go (1)
Device(34-45)internal/sms-gateway/modules/webhooks/repository_filter.go (1)
WithDeviceID(41-46)
internal/sms-gateway/handlers/messages/3rdparty.go (5)
internal/sms-gateway/handlers/devices/3rdparty.go (2)
NewThirdPartyController(24-36)ThirdPartyController(18-22)internal/sms-gateway/handlers/base/handler.go (1)
Handler(15-18)internal/sms-gateway/modules/devices/repository.go (1)
ErrNotFound(14-14)internal/sms-gateway/modules/messages/service.go (1)
EnqueueOptions(26-28)internal/sms-gateway/modules/messages/repository.go (1)
ErrMessageAlreadyExists(17-17)
internal/sms-gateway/handlers/mobile.go (1)
internal/sms-gateway/models/models.go (2)
Device(34-45)User(18-24)
pkg/cache/redis.go (3)
pkg/pubsub/redis.go (2)
NewRedis(42-78)RedisConfig(15-27)pkg/cache/errors.go (2)
ErrInvalidConfig(7-7)ErrKeyNotFound(9-9)pkg/cache/cache.go (1)
Cache(5-40)
internal/sms-gateway/modules/messages/repository.go (2)
internal/sms-gateway/modules/messages/repository_filter.go (3)
SelectFilter(16-23)SelectOptions(51-62)Order(7-7)internal/sms-gateway/modules/messages/models.go (2)
Message(35-55)ProcessingStatePending(16-16)
internal/sms-gateway/modules/messages/service.go (4)
internal/sms-gateway/modules/messages/repository_filter.go (3)
Order(7-7)SelectFilter(16-23)SelectOptions(51-62)internal/sms-gateway/modules/messages/domain.go (3)
MessageOut(25-29)MessageStateIn(31-36)MessageStateOut(38-44)internal/sms-gateway/modules/messages/models.go (3)
MessageState(129-134)ProcessingState(12-12)MessageRecipient(111-117)internal/sms-gateway/modules/messages/errors.go (1)
ValidationError(9-9)
internal/sms-gateway/modules/push/upstream/client.go (1)
internal/sms-gateway/modules/push/fcm/client.go (2)
New(21-27)Client(14-19)
internal/sms-gateway/handlers/settings/3rdparty.go (3)
internal/sms-gateway/handlers/messages/3rdparty.go (2)
NewThirdPartyController(44-53)ThirdPartyController(37-42)internal/sms-gateway/handlers/webhooks/3rdparty.go (2)
NewThirdPartyController(32-40)ThirdPartyController(26-30)internal/sms-gateway/modules/settings/models.go (1)
DeviceSettings(10-17)
pkg/pubsub/redis.go (2)
pkg/cache/redis.go (2)
NewRedis(81-108)RedisConfig(56-70)pkg/pubsub/pubsub.go (3)
ErrInvalidConfig(9-9)Subscription(19-24)PubSub(46-51)
internal/sms-gateway/modules/messages/cache.go (2)
internal/sms-gateway/modules/messages/domain.go (1)
MessageStateOut(38-44)pkg/cache/options.go (2)
WithTTL(20-31)AndSetTTL(65-69)
internal/sms-gateway/handlers/devices/3rdparty.go (4)
internal/sms-gateway/handlers/messages/3rdparty.go (2)
NewThirdPartyController(44-53)ThirdPartyController(37-42)internal/sms-gateway/handlers/settings/3rdparty.go (2)
NewThirdPartyController(35-44)ThirdPartyController(28-33)internal/sms-gateway/handlers/webhooks/3rdparty.go (2)
NewThirdPartyController(32-40)ThirdPartyController(26-30)internal/sms-gateway/handlers/logs/3rdparty.go (2)
NewThirdPartyController(24-31)ThirdPartyController(20-22)
internal/sms-gateway/modules/push/fcm/utils.go (1)
internal/sms-gateway/modules/push/types.go (1)
Event(17-17)
internal/sms-gateway/modules/push/fcm/client.go (3)
internal/sms-gateway/modules/push/upstream/client.go (1)
Client(22-27)internal/sms-gateway/modules/events/types.go (1)
Event(10-13)internal/sms-gateway/modules/push/types.go (1)
Event(17-17)
internal/sms-gateway/handlers/webhooks/3rdparty.go (7)
internal/sms-gateway/handlers/devices/3rdparty.go (2)
NewThirdPartyController(24-36)ThirdPartyController(18-22)internal/sms-gateway/handlers/messages/3rdparty.go (2)
NewThirdPartyController(44-53)ThirdPartyController(37-42)internal/sms-gateway/handlers/settings/3rdparty.go (2)
NewThirdPartyController(35-44)ThirdPartyController(28-33)internal/sms-gateway/handlers/logs/3rdparty.go (2)
NewThirdPartyController(24-31)ThirdPartyController(20-22)internal/sms-gateway/modules/webhooks/models.go (1)
Webhook(11-25)internal/sms-gateway/modules/webhooks/errors.go (1)
IsValidationError(34-36)internal/sms-gateway/modules/webhooks/repository_filter.go (1)
WithExtID(7-11)
internal/sms-gateway/pubsub/pubsub.go (3)
pkg/pubsub/options.go (1)
WithBufferSize(15-19)pkg/pubsub/memory.go (1)
NewMemory(24-38)pkg/pubsub/redis.go (2)
NewRedis(42-78)RedisConfig(15-27)
internal/sms-gateway/modules/auth/service.go (6)
internal/sms-gateway/online/service.go (2)
New(28-37)Service(14-17)internal/sms-gateway/handlers/middlewares/deviceauth/deviceauth.go (1)
New(20-44)internal/sms-gateway/models/models.go (4)
User(18-24)NewUser(26-32)Device(34-45)NewDevice(47-53)internal/sms-gateway/modules/auth/types.go (1)
OneTimeCode(15-18)pkg/crypto/passwords.go (2)
MakeBCryptHash(14-20)CompareBCryptHash(22-27)internal/sms-gateway/modules/auth/errors.go (1)
ErrAuthorizationFailed(6-6)
internal/sms-gateway/modules/push/service.go (4)
internal/sms-gateway/modules/push/fcm/client.go (2)
Client(14-19)New(21-27)internal/sms-gateway/modules/push/upstream/client.go (2)
Client(22-27)New(29-35)internal/sms-gateway/cache/factory.go (1)
Factory(16-18)internal/sms-gateway/modules/push/types.go (1)
Event(17-17)
🪛 actionlint (1.7.8)
.github/workflows/release.yml
67-67: the runner of "actions/checkout@v3" action is too old to run on GitHub Actions. update the action's version to fix this issue
(action)
🪛 checkmake (0.2.2)
Makefile
[warning] 10-10: Missing required phony target "all"
(minphony)
[warning] 10-10: Missing required phony target "clean"
(minphony)
[warning] 10-10: Missing required phony target "test"
(minphony)
[warning] 15-15: Target "all" should be declared PHONY.
(phonydeclared)
🪛 GitHub Actions: Go
internal/sms-gateway/modules/messages/service.go
[error] 236-236: do not define dynamic errors, use wrapped static errors instead: "errors.New("no text or data content")" (err113)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
- GitHub Check: goreleaser
🔇 Additional comments (60)
internal/sms-gateway/modules/messages/converters.go (2)
13-13: LGTM! Cleaner pointer initialization.Removing the redundant explicit
nilinitialization follows Go conventions—pointer variables default tonil.
21-21: LGTM! More professional error messages.The updated error messages using "failed to" instead of "can't" improve consistency and professionalism.
Also applies to: 25-25
internal/sms-gateway/pubsub/pubsub.go (2)
17-17: AI summary inconsistency detected.The AI summary mentions adding
ErrInvalidConfig, but the code declaresErrInvalidScheme.
19-51: Excellent error handling improvements.The addition of consistent error wrapping throughout the
Newfunction provides better error context and follows Go best practices. The unified error handling pattern for both memory and redis cases is clean and makes the code more maintainable.pkg/pubsub/redis.go (5)
29-40: LGTM! Type exposure aligns with refactor goals.The struct is now public and includes appropriate concurrency primitives for managing subscribers and goroutines.
80-96: LGTM! Enhanced error wrapping.The error wrapping improvement provides better context for debugging publish failures.
98-167: LGTM! Proper error wrapping and concurrency handling.The enhanced error wrapping and synchronization logic (mutex for map access, waitgroup for goroutine tracking) are correctly implemented.
169-186: LGTM! Proper resource cleanup and error handling.The enhanced error wrapping and waitgroup synchronization ensure all subscriber goroutines complete before closing the client.
188-188: LGTM! Type assertion updated correctly.The compile-time interface check now references the public
RedisPubSubtype..github/workflows/release.yml (3)
35-36: Clarify the RELEASE_ID calculation purpose.The RELEASE_ID calculation (days elapsed since 2022-06-15) is unusual for a release workflow. Clarify:
- Why is this timestamp offset needed?
- How is it used by GoReleaser or downstream processes?
- Is this for build IDs, version numbering, or diagnostics?
Consider documenting the intent if it serves a critical function.
38-46: Verify GoReleaser configuration exists and is properly set up.The workflow invokes GoReleaser but the
.goreleaser.yml(or.goreleaser.yaml) configuration file is not shown in the PR. Ensure:
- The configuration file exists at the repository root
- It defines build targets, docker image configuration, and publish settings matching your DOCKER_REGISTRY and multi-platform release needs
- All environment variables referenced (GITHUB_TOKEN, DOCKER_REGISTRY, RELEASE_ID) are correctly used in the config
This is critical for the release flow to work correctly.
69-72: Terraform version v1.13.5 is available and compatible—no action needed.v1.13.5 was released on November 5, 2025 and binaries are available on HashiCorp's releases. HashiCorp has pledged that configurations written for Terraform version 1.0 or newer will be compatible with all future releases of Terraform within major version 1.0. Since v1.13.5 is a patch release (only bug fixes, no breaking changes), your
.tffiles and deployment scripts indeployments/docker-swarm-terraformwill maintain compatibility.Makefile (6)
2-3: Clean separation of registry and image variables.The introduction of
registry_nameandimage_nameis well-structured for the GoReleaser migration.
10-13: Phony declaration is comprehensive and correctly formatted.All targets are properly declared. Note: Static analysis hints reporting missing PHONY targets are false positives—all required targets (
all,test,clean) are clearly listed.
26-28: Well-structured coverage target with proper dependency chain.The
coveragetarget correctly depends ontestand generates both summary and HTML reports.
39-41: Clean target properly removes goreleaser artifacts.The updated clean target now removes
coverage.*,benchmark.txt, and thedist/directory. This aligns well with the new build artifact locations from goreleaser.
43-44: Help target provides good discoverability for main targets.The awk-based help formatter correctly displays all targets with
##comments. Development helper targets (lines 48+) intentionally lack descriptions and remain hidden from help output, maintaining a clean user-facing interface.
36-37: No issues found—GoReleaser configuration is properly set up.The environment variables and flags in the release target are correctly configured:
- DOCKER_REGISTRY: Referenced in .goreleaser.yaml (
{{ .Env.DOCKER_REGISTRY }}/{{ .ProjectName }}) for Docker image builds and labels; set toghcr.io/android-sms-gatewayvia Makefile variable.- RELEASE_ID: Referenced in .goreleaser.yaml (
{{ .Env.RELEASE_ID }}) for the AppRelease ldflag; hardcoded to0for snapshot builds.- --snapshot --clean: Appropriate flags for creating clean snapshot releases without pushing artifacts.
The setup aligns with GoReleaser expectations and the intended snapshot release workflow.
pkg/cache/redis.go (9)
5-5: LGTM: Import added for error checking.The
errorspackage import is correctly added to supporterrors.Is()calls for robust error checking throughout the file.
44-46: LGTM: Defensive guard for negative TTL values.The guard correctly handles cases where
HTTLreturns -1 (no expiration) or -2 (key doesn't exist), preventing negative values in subsequent TTL calculations.
72-79: LGTM: Type exported consistently.The
RedisCachetype is now exported, which is a breaking API change but aligns with the PR's refactoring objectives. The constructor signature and return type are updated consistently.Also applies to: 81-81, 100-100
87-87: LGTM: Consistent error wrapping.Error wrapping with
ErrInvalidConfigfollows the established pattern frompkg/pubsub/redis.goand enables proper error checking witherrors.Is().
136-137: LGTM: Good optimization with capacity pre-allocation.The
itemsPerKeyconstant improves readability and the pre-allocation reduces memory allocations during map population.
156-157: LGTM: Proper error checking with errors.Is().Using
errors.Is(err, redis.Nil)is the correct approach for checking Redis "not found" errors, especially when errors may be wrapped.Also applies to: 181-182
118-118: LGTM: Consistent error messages and wrapping.Error messages are standardized to the "failed to..." pattern with proper error wrapping using
%w, maintaining error chains for inspection.Also applies to: 128-128, 160-160, 184-184, 198-198, 229-229, 239-239, 254-254, 264-264
195-196: LGTM: Method chaining formatted for readability.The chained
Result()call is correctly split across lines for improved readability.
271-271: LGTM: Type assertion updated correctly.The interface compliance check is updated to use the exported
RedisCachetype.internal/sms-gateway/modules/sse/service.go (7)
17-19: LGTM! Good practice to extract the magic number.Defining
eventsBufferSizeas a constant improves maintainability and makes the buffer size explicit and centrally configurable.
62-69: LGTM! Proper error wrapping and improved logging.The error wrapping with
%wenables proper error chain inspection, and the multiline log formatting enhances readability.Also applies to: 79-89, 98-98
120-131: LGTM! Clean separation of concerns.Extracting the streaming logic into
handleStreamimproves testability and maintains a clear separation between HTTP setup and core SSE streaming logic.
133-179: LGTM! Properly handles write errors and stream lifecycle.The new
handleStreammethod correctly addresses the previous review concern about write error handling. Thesuccessflag pattern (lines 150-163) ensures that whenwriteToStreamfails, the error is captured andhandleStreamexits properly, triggering the deferred connection cleanup.Additional observations:
- Conditional ticker creation (lines 137-145) correctly handles the case when keep-alive is disabled
- Keepalive metric is appropriately incremented only after successful writes (line 174)
- Connection lifecycle management with deferred cleanup is properly structured
Based on learnings
181-192: LGTM! Proper error wrapping with context.The descriptive error messages with
%wwrapping provide clear context for debugging write and flush failures while preserving the error chain.
194-218: LGTM! Uses the new constant.Properly uses
eventsBufferSizeconstant for channel buffer sizing, maintaining consistency with the constant definition.
220-245: LGTM! Improved log formatting.The multiline log format enhances readability while maintaining the same cleanup logic.
internal/sms-gateway/modules/messages/service.go (8)
77-88: LGTM! Clean defaulting behavior for pending message order.The function properly defaults to LIFO when
orderis empty and correctly delegates to the repository with appropriate error handling.
90-128: LGTM! Builder pattern and lo-based mapping correctly implemented.The function properly uses builder-style filters/options, and the
lo.MapToSlicemigration cleanly handles state mapping with appropriate defaults. Cache errors are correctly logged without interrupting the main flow.
130-143: LGTM! Builder-style signature correctly applied.The function signature aligns with the broader refactor to typed filters/options, and the enhanced error wrapping improves debuggability.
145-178: LGTM! Builder pattern correctly applied with proper cache handling.The function correctly uses builder-style filters/options and appropriately handles cache errors with logging.
267-275: LGTM! Error propagation aligns with explicit error handling pattern.The signature change from boolean to error-only return improves clarity and aligns with the broader pattern of explicit error propagation.
279-284: LGTM! Clean migration to lo-based mapping.The refactor to
lo.Mapwith thenewMessageRecipienthelper improves readability and eliminates manual slice construction.
286-312: LGTM! Refactor preserves hashing and state conversion logic.The migration to the
newMessageRecipienthelper maintains the phone number hashing and state compatibility logic correctly.
341-357: LGTM! ValidationError correctly replaces ErrValidation.The function properly uses
ValidationErrorfor phone validation failures, aligning with the broader error handling refactor.internal/sms-gateway/modules/auth/service.go (6)
71-99: LGTM! Clean migration to OneTimeCode type.The function correctly generates 6-digit codes using bitshift and modulus operations, with appropriate error handling and retry logic.
101-113: LGTM! Pointer semantics and constructor usage correctly applied.The function properly uses
models.NewUserand returns a pointer, aligning with the broader refactor to pointer-based model handling.
115-123: LGTM! Device registration correctly uses service-layer insertion.The function properly delegates device creation to
models.NewDeviceand persists viadevicesSvc.Insert, with appropriate error wrapping.
141-157: LGTM! Named timeout constant improves readability.The function correctly wraps
ErrAuthorizationFailedand uses a well-named timeout constant for the context deadline.
159-181: LGTM! Cache key collision fixed with separator.The cache key now includes
"\x00"as a separator (Line 160), preventing collisions between username/password combinations. The pointer-based return type aligns with the broader refactor.
227-246: LGTM! Proper lifecycle management for cache cleanup.The
Runmethod correctly implements periodic cache cleanup with proper context cancellation handling and ticker cleanup.internal/sms-gateway/modules/messages/repository.go (5)
30-101: LGTM! Builder pattern correctly applied to Select.The signature update to use
SelectFilterandSelectOptionsaligns with the broader refactor, and the pointer-based model initialization is appropriate.
103-110: LGTM! Builder pattern cleanly applied to SelectPending.The function correctly uses fluent builder methods to construct filters and options before delegating to
Select.
112-127: LGTM! Clean delegation to Select with proper error handling.The function correctly delegates to
Selectand handles zero/multiple result cases appropriately.
142-174: LGTM! Transaction handling with appropriate error wrapping.The function correctly wraps transaction errors and explicitly returns
nilon success, improving clarity.
195-202: LGTM! Pointer-based Delete invocation is correct.The use of
(*Message)(nil)aligns with the pointer-based pattern used throughout the refactor.internal/sms-gateway/modules/push/fcm/utils.go (1)
10-20: LGTM - Clean type migration.The function correctly migrates to the new
client.Eventtype and improves error message consistency.internal/sms-gateway/modules/push/types.go (2)
29-36: LGTM - Improved error handling.The explicit error wrapping and early return pattern improves clarity and consistency with error handling throughout the module.
38-44: LGTM - Consistent error handling.The deserialize method follows the same improved error handling pattern as serialize, ensuring consistency across the type.
internal/sms-gateway/modules/push/service.go (4)
19-41: LGTM - Improved configuration and type safety.The addition of
defaultDebounceconstant and migration toclient.Clientinterface improve code maintainability and flexibility.
43-73: LGTM - Consistent constructor refactoring.The constructor correctly adopts the new
client.Clientinterface and applies consistent error messaging patterns.
92-121: LGTM - Improved error handling clarity.The
setErrvariable naming and enhanced error messages improve code readability and debugging.
124-201: LGTM - Consistent refactoring throughout.The variable naming improvements (
wrapErr,sendErr) and type migrations toclient.Messagemaintain consistency with the broader refactoring effort.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 2
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (2)
internal/sms-gateway/handlers/upstream.go (1)
58-64: Handle empty request bodies without leakingEOF.
fiber.Ctx.BodyParserreturnsio.EOFwhen the body is empty, so Line 58 exits early and the new check on Line 62 never runs. Clients now see a 400 with messageEOFinstead of the intended"Empty request", so the feature doesn’t work as advertised and exposes an internal parse error. Treatio.EOFas the empty-body case before returning other parser errors.Apply this diff:
@@ -import ( +import ( + "errors" + "io" "time" @@ - if err := c.BodyParser(&req); err != nil { - return fiber.NewError(fiber.StatusBadRequest, err.Error()) + if err := c.BodyParser(&req); err != nil { + if errors.Is(err, io.EOF) { + return fiber.NewError(fiber.StatusBadRequest, "Empty request") + } + return fiber.NewError(fiber.StatusBadRequest, err.Error()) } if len(req) == 0 { return fiber.NewError(fiber.StatusBadRequest, "Empty request")internal/sms-gateway/modules/push/fcm/client.go (1)
57-87: Synchronize Send with Close to prevent nil client race
Closenow grabsc.muxand setsc.client = nil, butSendstill readsc.clientwithout coordination. If shutdown triggersClosewhile sends are in flight, you’ll race on that pointer and can crash when it flips to nil. Please guard the read with the same mutex (or equivalent) and bail out cleanly when the client isn’t initialized.func (c *Client) Send(ctx context.Context, messages []client.Message) ([]error, error) { - errs := make([]error, len(messages)) - - for i, message := range messages { + errs := make([]error, len(messages)) + + c.mux.Lock() + messagingClient := c.client + c.mux.Unlock() + + if messagingClient == nil { + err := fmt.Errorf("fcm client not initialized") + for i := range errs { + errs[i] = err + } + return errs, err + } + + for i, message := range messages { data, err := eventToMap(message.Event) if err != nil { errs[i] = fmt.Errorf("failed to marshal event: %w", err) continue } - _, err = c.client.Send(ctx, &messaging.Message{ + _, err = messagingClient.Send(ctx, &messaging.Message{ Data: data, Android: &messaging.AndroidConfig{ Priority: "high",
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (11)
.github/workflows/release.yml(2 hunks)internal/sms-gateway/handlers/base/handler.go(1 hunks)internal/sms-gateway/handlers/messages/mobile.go(3 hunks)internal/sms-gateway/handlers/settings/mobile.go(2 hunks)internal/sms-gateway/handlers/upstream.go(4 hunks)internal/sms-gateway/modules/messages/errors.go(1 hunks)internal/sms-gateway/modules/messages/service.go(11 hunks)internal/sms-gateway/modules/messages/workers.go(1 hunks)internal/sms-gateway/modules/push/fcm/client.go(4 hunks)internal/sms-gateway/online/service.go(5 hunks)pkg/cache/redis.go(12 hunks)
✅ Files skipped from review due to trivial changes (2)
- internal/sms-gateway/handlers/base/handler.go
- internal/sms-gateway/modules/messages/workers.go
🚧 Files skipped from review as they are similar to previous changes (2)
- internal/sms-gateway/online/service.go
- internal/sms-gateway/handlers/messages/mobile.go
🧰 Additional context used
🧠 Learnings (3)
📚 Learning: 2025-09-10T23:53:42.006Z
Learnt from: capcom6
Repo: android-sms-gateway/server PR: 178
File: pkg/cache/redis.go:63-75
Timestamp: 2025-09-10T23:53:42.006Z
Learning: In pkg/cache/redis.go, the Redis cache implementation uses Redis's native field-level TTL via HExpireAt, meaning expired fields are automatically removed by Redis itself. This differs from the memory implementation where the application tracks expiration times. As a result, operations like Drain that call HGetAll will only return non-expired items since expired fields are already removed by Redis.
Applied to files:
pkg/cache/redis.go
📚 Learning: 2025-09-10T23:54:16.828Z
Learnt from: capcom6
Repo: android-sms-gateway/server PR: 178
File: pkg/cache/redis.go:118-121
Timestamp: 2025-09-10T23:54:16.828Z
Learning: Redis 7.4.0 introduced HEXPIRE, HEXPIREAT, and related commands that support per-field TTL on hash data structures. go-redis v9.6.0+ includes support for these commands with methods like HExpireAt.
Applied to files:
pkg/cache/redis.go
📚 Learning: 2025-09-10T11:11:43.462Z
Learnt from: capcom6
Repo: android-sms-gateway/server PR: 178
File: pkg/cache/memory.go:59-74
Timestamp: 2025-09-10T11:11:43.462Z
Learning: In pkg/cache/memory.go, the cleanup method removes expired items first, then executes the provided callback function. This means methods like Drain that use cleanup don't need additional expiration filtering since expired items are already removed before the callback runs.
Applied to files:
pkg/cache/redis.go
🧬 Code graph analysis (4)
internal/sms-gateway/modules/messages/service.go (5)
internal/sms-gateway/modules/messages/repository_filter.go (3)
Order(7-7)SelectFilter(16-23)SelectOptions(51-62)internal/sms-gateway/modules/messages/models.go (3)
MessageState(129-134)ProcessingState(12-12)MessageRecipient(111-117)internal/sms-gateway/modules/messages/repository.go (1)
ErrMessageNotFound(16-16)internal/sms-gateway/modules/messages/errors.go (2)
ErrNoContent(7-7)ValidationError(10-10)internal/sms-gateway/modules/events/events.go (1)
NewMessageEnqueuedEvent(9-11)
pkg/cache/redis.go (4)
pkg/pubsub/redis.go (2)
NewRedis(42-78)RedisConfig(15-27)pkg/cache/errors.go (2)
ErrInvalidConfig(7-7)ErrKeyNotFound(9-9)pkg/cache/options.go (3)
GetOption(49-49)AndDelete(89-93)Option(6-6)pkg/cache/cache.go (1)
Cache(5-40)
internal/sms-gateway/modules/push/fcm/client.go (4)
internal/sms-gateway/modules/push/fcm/errors.go (1)
ErrInitializationFailed(6-6)internal/sms-gateway/modules/push/upstream/client.go (1)
Client(22-27)internal/sms-gateway/modules/events/types.go (1)
Event(10-13)internal/sms-gateway/modules/push/types.go (1)
Event(17-17)
internal/sms-gateway/handlers/settings/mobile.go (4)
internal/sms-gateway/handlers/base/handler.go (1)
Handler(15-18)internal/sms-gateway/handlers/messages/mobile.go (2)
NewMobileController(35-43)MobileController(29-33)internal/sms-gateway/handlers/webhooks/mobile.go (2)
NewMobileController(21-33)MobileController(15-19)internal/sms-gateway/models/models.go (1)
Device(34-45)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (4)
- GitHub Check: E2E
- GitHub Check: Test
- GitHub Check: Benchmark
- GitHub Check: Analyze (go)
🔇 Additional comments (13)
.github/workflows/release.yml (4)
35-36: Verify the purpose of RELEASE_ID calculation.The RELEASE_ID is calculated as days elapsed since 2022-06-15. Confirm this is intentional and that it's referenced in your
.goreleaser.yamlconfiguration. If this env var is unused, consider removing it to reduce noise.
70-72: Verify Terraform version currency.Terraform 1.13.5 is quite old (over two years behind the current major version). Confirm this pinned version is intentional for your infrastructure compatibility, or consider updating to a more recent stable version.
56-56: Approve goreleaser job dependency structure.The deploy jobs correctly depend on the goreleaser job completing first, which is the right ordering for a tag-triggered release workflow.
Also applies to: 99-99
63-64: Approve APP_VERSION environment variable extraction.The APP_VERSION extraction from the git tag (via
GITHUB_REF#refs/tags/) is the correct pattern for tag-based releases. This cleanly replaces the previousneeds.build.outputs.app-versionapproach.internal/sms-gateway/handlers/settings/mobile.go (3)
21-35: LGTM: Clean constructor migration.The explicit dependency injection pattern is consistent with the webhooks controller and improves testability.
51-57: Previous security issue resolved.The error handling now correctly logs detailed context server-side while returning a generic error to the client, preventing the user ID and device ID leak flagged in the earlier review.
9-9: LGTM: Validator dependency added.The validator import supports the new constructor signature and validation workflow.
pkg/cache/redis.go (6)
44-46: Good defensive measure: TTL clamping.The clamping of negative TTL values to 0 is correct. When
HTTLreturns -1 (field exists but has no TTL), clamping to 0 ensures thatupdateTTLextends the TTL by the delta starting from 0, which is the expected behavior.
72-72: Breaking change: RedisCache is now exported.The type has been renamed from
redisCachetoRedisCache, making it part of the public API. This is consistent with similar exported types likeRedisPubSubin pkg/pubsub/redis.go.
87-87: Excellent error handling improvements.The addition of
ErrInvalidConfigwrapping and the use oferrors.Isfor checkingredis.Nilare best practices. All error messages now provide proper context through wrapping with%w.Also applies to: 156-160
136-137: Good use of named constant.Using the
itemsPerKeyconstant improves code readability and makes the map capacity calculation self-documenting.
251-259: LGTM: Improved error handling in Close.The Close method now properly wraps errors from the Redis client with context, maintaining consistency with error handling throughout the file.
148-196: Delete flag is now correctly handled.The verification confirms that
isEmpty()properly checks thedeletefield at line 62 (!o.delete). Whendelete=true,isEmpty()returnsfalse, ensuring the code takes the Lua script path even without TTL options. The refactoring successfully resolves the past review concern.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 1
🧹 Nitpick comments (1)
.github/workflows/release.yml (1)
16-19: Note: fetch-depth inconsistency between checkout steps.The goreleaser job's checkout includes
fetch-depth: 0(line 19), while the deploy job's checkout (line 67) does not. If the deploy job needs the full git history (e.g., for changelog or versioning logic), consider addingfetch-depth: 0to line 67 as well for consistency and to avoid potential issues with shallow clones.Also applies to: 66-67
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (2)
.github/workflows/release.yml(2 hunks)internal/sms-gateway/modules/messages/service.go(11 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
internal/sms-gateway/modules/messages/service.go (6)
internal/sms-gateway/modules/messages/repository_filter.go (3)
Order(7-7)SelectFilter(16-23)SelectOptions(51-62)internal/sms-gateway/modules/messages/domain.go (3)
MessageOut(25-29)MessageStateIn(31-36)MessageStateOut(38-44)internal/sms-gateway/modules/messages/models.go (3)
MessageState(129-134)ProcessingState(12-12)MessageRecipient(111-117)internal/sms-gateway/modules/messages/repository.go (1)
ErrMessageNotFound(16-16)internal/sms-gateway/modules/messages/errors.go (2)
ErrNoContent(7-7)ValidationError(10-10)internal/sms-gateway/modules/events/events.go (1)
NewMessageEnqueuedEvent(9-11)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (4)
- GitHub Check: E2E
- GitHub Check: Benchmark
- GitHub Check: Test
- GitHub Check: Analyze (go)
🔇 Additional comments (8)
.github/workflows/release.yml (3)
35-36: Clarify the purpose of the RELEASE_ID calculation.The RELEASE_ID is derived from days elapsed since a hardcoded date (2022-06-15) rather than from the git tag or GitHub release metadata. This approach is unconventional for release workflows. Please clarify if this is intentional (e.g., for docker image tagging or custom versioning) or if it should use the git tag directly.
16-19: Checkout action versions are now consistent.Both the goreleaser job (line 17) and deploy job (line 67) now use
actions/checkout@v5. This resolves the earlier version mismatch flagged in prior reviews.Also applies to: 66-67
69-72: Terraform version 1.13.5 is correct and current.The latest stable Terraform release is v1.13.5 (released November 5, 2025). The workflow specifies an appropriate, up-to-date version with no changes needed.
internal/sms-gateway/modules/messages/service.go (5)
18-18: LGTM: Clean functional helper usage.The addition of the
lolibrary and its usage forMapoperations makes the code more concise and idiomatic.Also applies to: 83-83, 276-279
87-90: LGTM: Builder pattern improves clarity.The builder-style filter and options construction enhances readability and makes the optional parameters explicit.
Also applies to: 155-156
232-232: Excellent: Both past review comments addressed correctly.Line 232 now uses the static
ErrNoContentsentinel error, and line 244 correctly caches with the persistedmsg.ExtIDinstead of the potentially emptymessage.ID. These changes resolve the concerns raised in previous reviews.Also applies to: 244-244
251-257: LGTM: Enhanced structured logging.The additional context fields (
user_id,device_id) in error logs improve debuggability and tracing.
100-110: Reconsider OnConflict handling for state updates.The repository's
UpdateStatemethod (repository.go:150-152) usesOnConflict{DoNothing: true}to handle the unique constraint on(MessageID, State). While this prevents SQL errors, it silently skips inserts when a state with the same(MessageID, State)pair already exists—meaning the existing record'sUpdatedAttimestamp is never refreshed.If the intent is to upsert (update or insert), consider replacing
DoNothingwithDoUpdatesto preserve the latest timestamp:Clauses(clause.OnConflict{ UpdateAll: true, }).Create(&v)Otherwise, clarify whether ignoring duplicate state updates is intentional.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 0
🧹 Nitpick comments (1)
internal/worker/config/config.go (1)
57-59: Consider removing redundant zero-value initialization.The explicit initialization of
Debug: false,MaxOpenConns: 0, andMaxIdleConns: 0is redundant since Go automatically initializes these fields to their zero values. Omitting them would make the code slightly cleaner.Apply this diff to remove redundant zero values:
Database: config.Database{ Host: "localhost", Port: 3306, User: "sms", Password: "sms", Database: "sms", Timezone: "UTC", - Debug: false, - MaxOpenConns: 0, - MaxIdleConns: 0, },
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (2)
internal/worker/app.go(1 hunks)internal/worker/config/config.go(1 hunks)
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2025-09-10T23:45:45.502Z
Learnt from: capcom6
Repo: android-sms-gateway/server PR: 178
File: internal/sms-gateway/online/module.go:14-16
Timestamp: 2025-09-10T23:45:45.502Z
Learning: fx.Decorate in uber-go/fx creates module-scoped decorated dependencies, not global modifications. Using fx.Decorate(func(log *zap.Logger) *zap.Logger { return log.Named("module_name") }) is a correct pattern for creating module-specific loggers that are scoped only to that module's dependency graph.
Applied to files:
internal/worker/app.go
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (4)
- GitHub Check: E2E
- GitHub Check: Benchmark
- GitHub Check: Test
- GitHub Check: Analyze (go)
🔇 Additional comments (2)
internal/worker/config/config.go (1)
35-35: LGTM! Appropriate lint suppression for default config.The nolint directive is well-justified for suppressing
exhaustructandmndwarnings in a default configuration function where magic numbers (time intervals) are self-documenting and zero values are intentional.internal/worker/app.go (1)
38-49: LGTM! Good use of blank identifier for unused parameters.Using
_for the unused context parameters is idiomatic Go and makes it explicit that these lifecycle hooks don't need the context. Since both hooks only log messages and return immediately, ignoring the context is perfectly acceptable.
b3ce222 to
e54f0eb
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 1
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (3)
pkg/cache/memory_edge_test.go (1)
64-83: Test name no longer matches implementation.The test is named
TestMemoryCache_NilContextbut now usescontext.Background()on lines 70 and 75 instead of a nil context. Consider renaming the test to reflect its actual behavior (e.g.,TestMemoryCache_BasicOperationsorTestMemoryCache_ContextUsage), or adjust the test to actually validate nil context handling if that was the original intent.internal/sms-gateway/modules/sse/service.go (1)
224-233: Avoid closingcloseSignaltwice to prevent panics.
Service.Closealready closes everyconn.closeSignal. When that happens,handleStreamreturns and the deferredremoveConnectionhits this block, attempting to close the same channel a second time—which panics the whole goroutine and leaves metrics in an inconsistent state. Guard the close with async.Once(or equivalent) so the signal channel is only closed once regardless of who initiates shutdown.type sseConnection struct { id string channel chan eventWrapper closeSignal chan struct{} + closeOnce sync.Once } @@ func (s *Service) Close(_ context.Context) error { s.mu.Lock() defer s.mu.Unlock() for deviceID, connections := range s.connections { for _, conn := range connections { - close(conn.closeSignal) + conn.closeOnce.Do(func() { close(conn.closeSignal) }) } delete(s.connections, deviceID) } return nil } @@ func (s *Service) removeConnection(deviceID, connID string) { s.mu.Lock() defer s.mu.Unlock() if connections, exists := s.connections[deviceID]; exists { for i, conn := range connections { if conn.id == connID { - close(conn.closeSignal) + conn.closeOnce.Do(func() { close(conn.closeSignal) }) s.connections[deviceID] = append(connections[:i], connections[i+1:]...) s.logger.Info( "Removing SSE connection", zap.String("device_id", deviceID), zap.String("connection_id", connID), ) break } }internal/sms-gateway/modules/push/fcm/client.go (1)
57-88: Prevent race between Send and Close.
Closeflipsc.clienttonilwhile holdingmux, butSendreads and usesc.clientwithout any synchronization. If a shutdown callsClosewhileSendis iterating, the pointer can change mid-loop, causing a data race and even a nil dereference panic. Please grab the mutex (or copy the pointer under the same mutex) before using it and fail fast when the client hasn’t been opened.func (c *Client) Send(ctx context.Context, messages []client.Message) ([]error, error) { - errs := make([]error, len(messages)) - - for i, message := range messages { + errs := make([]error, len(messages)) + + c.mux.Lock() + messagingClient := c.client + c.mux.Unlock() + + if messagingClient == nil { + return errs, fmt.Errorf("client is not initialized") + } + + for i, message := range messages { data, err := eventToMap(message.Event) if err != nil { errs[i] = fmt.Errorf("failed to marshal event: %w", err) continue } - _, err = c.client.Send(ctx, &messaging.Message{ + _, err = messagingClient.Send(ctx, &messaging.Message{ Data: data, Android: &messaging.AndroidConfig{ Priority: "high", }, Token: message.Token,
♻️ Duplicate comments (9)
.github/workflows/release.yml (1)
17-17: Action version inconsistency with go.yml workflow.This workflow uses
actions/checkout@v5andactions/setup-go@v6, while the Go workflow (go.yml) usesactions/checkout@v4andactions/setup-go@v5. Past review feedback indicates this inconsistency was addressed, but it remains unresolved.Align the versions:
- Update
actions/checkoutto v4 (or justify why v5 is needed here)- Update
actions/setup-goto v5 (or justify why v6 is needed here)If v5/v6 are intentional, update go.yml to match for consistency across all workflows.
Also applies to: 31-31
pkg/cache/memory_profile_test.go (1)
243-243: Check error return from Drain (previously flagged).The error return from
Drainis still being ignored here. This was flagged in a previous review and remains unresolved. Inconsistent error handling could make memory leak detection results inaccurate ifDrainfails.Apply this diff to add consistent error checking:
- tempCache.Drain(ctx) + _, err := tempCache.Drain(ctx) + if err != nil { + t.Errorf("Drain failed: %v", err) + }pkg/cache/options.go (1)
22-29: Critical:ttl < 0still doesn't expire items immediately (duplicate concern).This exact issue was extensively flagged in past reviews: setting
validUntil = time.Now()for negative TTL won't work with strictAfter()comparisons sincenow.After(now)returnsfalse. Items that should expire immediately will not be treated as expired.The past review suggested using
time.Now().Add(ttl)forttl < 0to produce a past timestamp that will always be considered expired. Despite being marked as "✅ Addressed in commit 56b30fe" in previous comments, the problematic code is still present.Apply the previously suggested fix:
switch { case ttl == 0: o.validUntil = time.Time{} case ttl < 0: - o.validUntil = time.Now() + o.validUntil = time.Now().Add(ttl) default: o.validUntil = time.Now().Add(ttl) }internal/sms-gateway/handlers/mobile.go (1)
275-276: Handle or document the ignored error.The error from
nanoid.Standard(idGenSize)is still being ignored. While size 21 is valid and unlikely to fail, ignoring errors during initialization can hide unexpected failures and make debugging difficult. Please address this by either:
- Handling the error with a fatal log:
const idGenSize = 21 idGen, err := nanoid.Standard(idGenSize) if err != nil { params.Logger.Fatal("failed to initialize ID generator", zap.Error(err)) }
- Or documenting why it's safe to ignore:
const idGenSize = 21 // nanoid.Standard with size 21 never returns an error idGen, _ := nanoid.Standard(idGenSize)internal/sms-gateway/handlers/messages/params.go (1)
25-26: Parse dates before comparing
p.StartDate > p.EndDateis a lexicographical string comparison, so offsets like"2025-11-10T12:00:00+02:00"versus"2025-11-10T11:00:00+00:00"are ordered incorrectly. Parse both timestamps and comparetime.Timevalues instead; otherwise valid requests are rejected.- if p.StartDate != "" && p.EndDate != "" && p.StartDate > p.EndDate { - return errors.New("`from` date must be before `to` date") //nolint:err113 // won't be used directly - } + if p.StartDate != "" && p.EndDate != "" { + from, err := time.Parse(time.RFC3339, p.StartDate) + if err != nil { + return errors.New("`from` date must be before `to` date") //nolint:err113 // won't be used directly + } + + to, err := time.Parse(time.RFC3339, p.EndDate) + if err != nil { + return errors.New("`from` date must be before `to` date") //nolint:err113 // won't be used directly + } + + if from.After(to) { + return errors.New("`from` date must be before `to` date") //nolint:err113 // won't be used directly + } + }internal/config/module.go (2)
26-37: Wrap Config provider withfx.Annotateto applyfx.Private
Passingfx.Privateas a second argument makes Fx treat it like another constructor, causing startup failures. Wrap the provider withfx.Annotate(..., fx.Private)so the config remains module-private.- fx.Provide( - func(log *zap.Logger) Config { + fx.Provide( + fx.Annotate( + func(log *zap.Logger) Config { defaultConfig := Default() if err := config.LoadConfig(&defaultConfig); err != nil { log.Error("Error loading config", zap.Error(err)) } return defaultConfig - }, - fx.Private, + }, + fx.Private, + ), ),
89-103: Don’t overwrite user-specified API path when host is empty
Defaultingcfg.HTTP.API.Pathwhenever the host is blank clobbers legitimate custom paths (e.g., host omitted but path provided). Only set the default when the path itself is empty so user configuration survives.- if cfg.HTTP.API.Host == "" { + if cfg.HTTP.API.Path == "" { cfg.HTTP.API.Path = "/api" }internal/sms-gateway/modules/auth/service.go (1)
55-56: Document or handle the ignored error.The error from
nanoid.Standard(idLen)is silently discarded. While21is a valid size, add a comment explaining why it's safe to ignore, or handle the error.const idLen = 21 + // nanoid.Standard with size 21 never returns an error idgen, _ := nanoid.Standard(idLen)internal/sms-gateway/modules/messages/service.go (1)
276-279: Use explicitProcessingStatePendinginstead of empty string.The empty string
""passed as the state parameter is not a validProcessingStateenum value. Application logic should explicitly useProcessingStatePendingfor type safety and clarity.func (s *Service) recipientsToModel(input []string) []MessageRecipient { return lo.Map( input, - func(item string, _ int) MessageRecipient { return newMessageRecipient(item, "", nil) }, + func(item string, _ int) MessageRecipient { return newMessageRecipient(item, ProcessingStatePending, nil) }, ) }
🧹 Nitpick comments (7)
internal/worker/executor/service.go (1)
72-73: Improve the nolint comment to explain the reasoning.The nolint directive is appropriate since the random number is used for scheduling jitter, not cryptographic purposes. However, the comment "it's ok" doesn't explain why the suppression is justified.
Apply this diff to make the reasoning clear:
- //nolint:gosec // it's ok + //nolint:gosec // G404: weak random is acceptable for scheduling jitter initialDelay := time.Duration(math.Floor(rand.Float64()*task.Interval().Seconds())) * time.Secondinternal/sms-gateway/handlers/root.go (1)
28-28: LGTM! Lint suppression is appropriate.The
wrapchecksuppression is correct for this middleware pattern, as the error is passed through to Fiber's error handling mechanism. The middleware's purpose is to rewrite Location headers, and wrapping the error here wouldn't add meaningful context.Optionally, consider making the comment more specific:
- return err //nolint:wrapcheck //wrapped internally + return err //nolint:wrapcheck // passed through to fiber's error handlerpkg/cache/memory_concurrency_test.go (1)
399-399: Consider keeping the parameter name for clarity.While using
_ *testing.Tis syntactically valid, it's more conventional to keep the parameter name (e.g.,t *testing.T) even when unused. This maintains consistency with standard Go test signatures and makes the function purpose immediately clear.Apply this diff if you prefer the conventional style:
-func TestMemoryCache_RaceConditionDetection(_ *testing.T) { +func TestMemoryCache_RaceConditionDetection(t *testing.T) {internal/sms-gateway/modules/settings/utils.go (2)
62-62: Acknowledged: Complexity deferred with nolint.The nolint annotation pragmatically acknowledges the nested complexity. Consider refactoring to reduce nesting when time permits, but the current approach is acceptable.
90-90: Acknowledged: Complexity deferred with nolint.The nolint annotation pragmatically acknowledges the cognitive complexity. Consider extracting helper functions when refactoring, but the current approach is acceptable.
internal/sms-gateway/handlers/webhooks/3rdparty.go (1)
75-92: Consider the idiomatic Go pattern for this method.The review comment is accurate. The code creates a pointer with
new()solely to dereference it for theReplace()call, which takes a value. While functional, this is not idiomatic Go.Service.Replace expects a value (
webhook smsgateway.Webhook), not a pointer. The suggested refactoring using a value type (var dto smsgateway.Webhookwith&dtopassed toBodyParserValidator) is the standard Go pattern and avoids unnecessary pointer allocation. This approach is already used in other handlers (e.g., messages/3rdparty.go line 79-80) and is cleaner.Since this is a style improvement with no functional issues, you may apply the suggested change or leave the current implementation as-is—both work correctly.
internal/sms-gateway/pubsub/pubsub.go (1)
32-44: Consider simplifying the memory case assignment.The comma expression on line 35
pubSub, err = pubsub.NewMemory(opts...), nilis correct but non-idiomatic. SinceNewMemorydoesn't return an error, consider making this clearer:Apply this diff for better clarity:
var pubSub PubSub + var err error switch u.Scheme { case "memory": - pubSub, err = pubsub.NewMemory(opts...), nil + pubSub = pubsub.NewMemory(opts...) case "redis": pubSub, err = pubsub.NewRedis(pubsub.RedisConfig{
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (107)
.github/workflows/docker-build.yml(0 hunks).github/workflows/docker-publish.yml(0 hunks).github/workflows/go.yml(3 hunks).github/workflows/pr.yml(1 hunks).github/workflows/release.yml(2 hunks).gitignore(1 hunks).golangci.yml(1 hunks).goreleaser.yaml(1 hunks)Dockerfile.goreleaser(1 hunks)Makefile(1 hunks)internal/config/config.go(3 hunks)internal/config/module.go(1 hunks)internal/sms-gateway/app.go(2 hunks)internal/sms-gateway/cache/errors.go(1 hunks)internal/sms-gateway/cache/factory.go(2 hunks)internal/sms-gateway/handlers/base/handler.go(1 hunks)internal/sms-gateway/handlers/base/handler_test.go(5 hunks)internal/sms-gateway/handlers/converters/messages.go(1 hunks)internal/sms-gateway/handlers/devices/3rdparty.go(4 hunks)internal/sms-gateway/handlers/events/mobile.go(1 hunks)internal/sms-gateway/handlers/health.go(2 hunks)internal/sms-gateway/handlers/logs/3rdparty.go(2 hunks)internal/sms-gateway/handlers/messages/3rdparty.go(8 hunks)internal/sms-gateway/handlers/messages/mobile.go(3 hunks)internal/sms-gateway/handlers/messages/params.go(3 hunks)internal/sms-gateway/handlers/middlewares/userauth/userauth.go(2 hunks)internal/sms-gateway/handlers/mobile.go(8 hunks)internal/sms-gateway/handlers/module.go(1 hunks)internal/sms-gateway/handlers/root.go(1 hunks)internal/sms-gateway/handlers/settings/3rdparty.go(5 hunks)internal/sms-gateway/handlers/settings/mobile.go(2 hunks)internal/sms-gateway/handlers/upstream.go(4 hunks)internal/sms-gateway/handlers/webhooks/3rdparty.go(4 hunks)internal/sms-gateway/handlers/webhooks/mobile.go(2 hunks)internal/sms-gateway/models/migration.go(2 hunks)internal/sms-gateway/models/models.go(2 hunks)internal/sms-gateway/models/module.go(1 hunks)internal/sms-gateway/modules/auth/errors.go(1 hunks)internal/sms-gateway/modules/auth/module.go(1 hunks)internal/sms-gateway/modules/auth/repository.go(1 hunks)internal/sms-gateway/modules/auth/service.go(6 hunks)internal/sms-gateway/modules/auth/types.go(1 hunks)internal/sms-gateway/modules/db/health.go(3 hunks)internal/sms-gateway/modules/db/module.go(2 hunks)internal/sms-gateway/modules/devices/repository.go(4 hunks)internal/sms-gateway/modules/devices/repository_filter.go(1 hunks)internal/sms-gateway/modules/devices/service.go(6 hunks)internal/sms-gateway/modules/events/errors.go(1 hunks)internal/sms-gateway/modules/events/metrics.go(3 hunks)internal/sms-gateway/modules/events/module.go(1 hunks)internal/sms-gateway/modules/events/service.go(7 hunks)internal/sms-gateway/modules/events/types.go(2 hunks)internal/sms-gateway/modules/messages/cache.go(2 hunks)internal/sms-gateway/modules/messages/converters.go(1 hunks)internal/sms-gateway/modules/messages/errors.go(1 hunks)internal/sms-gateway/modules/messages/models.go(6 hunks)internal/sms-gateway/modules/messages/module.go(1 hunks)internal/sms-gateway/modules/messages/repository.go(5 hunks)internal/sms-gateway/modules/messages/repository_filter.go(2 hunks)internal/sms-gateway/modules/messages/service.go(11 hunks)internal/sms-gateway/modules/messages/service_test.go(0 hunks)internal/sms-gateway/modules/messages/workers.go(1 hunks)internal/sms-gateway/modules/metrics/handler.go(1 hunks)internal/sms-gateway/modules/metrics/module.go(1 hunks)internal/sms-gateway/modules/push/client.go(1 hunks)internal/sms-gateway/modules/push/client/types.go(1 hunks)internal/sms-gateway/modules/push/fcm/client.go(4 hunks)internal/sms-gateway/modules/push/fcm/errors.go(1 hunks)internal/sms-gateway/modules/push/fcm/utils.go(1 hunks)internal/sms-gateway/modules/push/module.go(2 hunks)internal/sms-gateway/modules/push/service.go(10 hunks)internal/sms-gateway/modules/push/types.go(3 hunks)internal/sms-gateway/modules/push/upstream/client.go(4 hunks)internal/sms-gateway/modules/settings/models.go(1 hunks)internal/sms-gateway/modules/settings/module.go(1 hunks)internal/sms-gateway/modules/settings/repository.go(5 hunks)internal/sms-gateway/modules/settings/service.go(3 hunks)internal/sms-gateway/modules/settings/utils.go(4 hunks)internal/sms-gateway/modules/sse/config.go(2 hunks)internal/sms-gateway/modules/sse/errors.go(1 hunks)internal/sms-gateway/modules/sse/metrics.go(3 hunks)internal/sms-gateway/modules/sse/module.go(1 hunks)internal/sms-gateway/modules/sse/service.go(8 hunks)internal/sms-gateway/modules/webhooks/errors.go(3 hunks)internal/sms-gateway/modules/webhooks/models.go(1 hunks)internal/sms-gateway/modules/webhooks/module.go(1 hunks)internal/sms-gateway/modules/webhooks/repository.go(2 hunks)internal/sms-gateway/modules/webhooks/repository_filter.go(1 hunks)internal/sms-gateway/modules/webhooks/service.go(5 hunks)internal/sms-gateway/online/service.go(5 hunks)internal/sms-gateway/openapi/docs.go(5 hunks)internal/sms-gateway/pubsub/module.go(2 hunks)internal/sms-gateway/pubsub/pubsub.go(2 hunks)internal/worker/app.go(1 hunks)internal/worker/config/config.go(1 hunks)internal/worker/config/types.go(2 hunks)internal/worker/executor/metrics.go(1 hunks)internal/worker/executor/service.go(3 hunks)pkg/cache/errors.go(1 hunks)pkg/cache/memory.go(12 hunks)pkg/cache/memory_bench_test.go(19 hunks)pkg/cache/memory_concurrency_test.go(12 hunks)pkg/cache/memory_edge_test.go(8 hunks)pkg/cache/memory_profile_test.go(2 hunks)pkg/cache/memory_test.go(8 hunks)pkg/cache/options.go(1 hunks)pkg/cache/redis.go(12 hunks)
⛔ Files not processed due to max files limit (11)
- pkg/crypto/passwords.go
- pkg/health/health.go
- pkg/health/module.go
- pkg/health/service.go
- pkg/health/types.go
- pkg/pubsub/memory.go
- pkg/pubsub/options.go
- pkg/pubsub/pubsub.go
- pkg/pubsub/redis.go
- test/e2e/mobile_test.go
- test/e2e/utils_test.go
💤 Files with no reviewable changes (3)
- .github/workflows/docker-build.yml
- internal/sms-gateway/modules/messages/service_test.go
- .github/workflows/docker-publish.yml
✅ Files skipped from review due to trivial changes (3)
- internal/sms-gateway/modules/sse/metrics.go
- internal/sms-gateway/modules/messages/module.go
- .gitignore
🚧 Files skipped from review as they are similar to previous changes (33)
- internal/sms-gateway/modules/sse/errors.go
- internal/sms-gateway/handlers/base/handler_test.go
- internal/sms-gateway/models/module.go
- internal/sms-gateway/online/service.go
- internal/sms-gateway/modules/events/errors.go
- internal/sms-gateway/modules/webhooks/service.go
- internal/sms-gateway/modules/messages/workers.go
- internal/worker/executor/metrics.go
- internal/sms-gateway/modules/devices/repository_filter.go
- .goreleaser.yaml
- internal/sms-gateway/modules/messages/converters.go
- internal/sms-gateway/modules/push/client.go
- internal/worker/app.go
- pkg/cache/memory_test.go
- internal/sms-gateway/modules/auth/errors.go
- internal/sms-gateway/modules/push/fcm/errors.go
- pkg/cache/errors.go
- internal/sms-gateway/modules/messages/cache.go
- internal/sms-gateway/modules/devices/repository.go
- internal/sms-gateway/cache/factory.go
- internal/sms-gateway/modules/events/service.go
- internal/sms-gateway/modules/metrics/handler.go
- pkg/cache/memory.go
- internal/sms-gateway/handlers/upstream.go
- Dockerfile.goreleaser
- internal/sms-gateway/modules/messages/repository_filter.go
- internal/worker/config/config.go
- internal/sms-gateway/handlers/settings/3rdparty.go
- .github/workflows/pr.yml
- internal/sms-gateway/handlers/converters/messages.go
- internal/sms-gateway/modules/push/fcm/utils.go
- internal/sms-gateway/modules/webhooks/repository_filter.go
- internal/sms-gateway/modules/settings/models.go
🧰 Additional context used
🧠 Learnings (6)
📚 Learning: 2025-09-10T23:45:45.502Z
Learnt from: capcom6
Repo: android-sms-gateway/server PR: 178
File: internal/sms-gateway/online/module.go:14-16
Timestamp: 2025-09-10T23:45:45.502Z
Learning: fx.Decorate in uber-go/fx creates module-scoped decorated dependencies, not global modifications. Using fx.Decorate(func(log *zap.Logger) *zap.Logger { return log.Named("module_name") }) is a correct pattern for creating module-specific loggers that are scoped only to that module's dependency graph.
Applied to files:
internal/sms-gateway/modules/settings/module.gointernal/sms-gateway/modules/metrics/module.gointernal/sms-gateway/handlers/module.gointernal/config/module.gointernal/sms-gateway/modules/auth/module.gointernal/sms-gateway/modules/events/module.gointernal/sms-gateway/modules/webhooks/module.gointernal/sms-gateway/pubsub/module.go
📚 Learning: 2025-09-10T23:53:42.006Z
Learnt from: capcom6
Repo: android-sms-gateway/server PR: 178
File: pkg/cache/redis.go:63-75
Timestamp: 2025-09-10T23:53:42.006Z
Learning: In pkg/cache/redis.go, the Redis cache implementation uses Redis's native field-level TTL via HExpireAt, meaning expired fields are automatically removed by Redis itself. This differs from the memory implementation where the application tracks expiration times. As a result, operations like Drain that call HGetAll will only return non-expired items since expired fields are already removed by Redis.
Applied to files:
pkg/cache/memory_edge_test.gopkg/cache/redis.gopkg/cache/options.go
📚 Learning: 2025-09-10T11:11:43.462Z
Learnt from: capcom6
Repo: android-sms-gateway/server PR: 178
File: pkg/cache/memory.go:59-74
Timestamp: 2025-09-10T11:11:43.462Z
Learning: In pkg/cache/memory.go, the cleanup method removes expired items first, then executes the provided callback function. This means methods like Drain that use cleanup don't need additional expiration filtering since expired items are already removed before the callback runs.
Applied to files:
pkg/cache/memory_edge_test.gopkg/cache/redis.gopkg/cache/memory_concurrency_test.gopkg/cache/memory_bench_test.gopkg/cache/memory_profile_test.go
📚 Learning: 2025-07-22T00:31:28.796Z
Learnt from: capcom6
Repo: android-sms-gateway/server PR: 154
File: internal/sms-gateway/modules/sse/service.go:67-79
Timestamp: 2025-07-22T00:31:28.796Z
Learning: In the SSE service Send method (internal/sms-gateway/modules/sse/service.go), blocking on full channels is intentional and acceptable. Timeout control is handled at the calling code level through context, not within the Send method itself. The user prefers this approach over adding a default case that would drop messages when channels are full.
Applied to files:
internal/sms-gateway/modules/sse/service.go
📚 Learning: 2025-07-22T00:34:31.896Z
Learnt from: capcom6
Repo: android-sms-gateway/server PR: 154
File: internal/sms-gateway/modules/sse/service.go:89-92
Timestamp: 2025-07-22T00:34:31.896Z
Learning: In Go SSE implementations, when cleaning up connections with buffered channels, avoid closing the data channels directly as this can cause panics if concurrent goroutines attempt to write to them. Instead, use a separate close signal channel to notify writers that the connection should be terminated, allowing the data channels to be garbage collected naturally when the connection is removed.
Applied to files:
internal/sms-gateway/modules/sse/service.go
📚 Learning: 2025-09-10T23:54:16.828Z
Learnt from: capcom6
Repo: android-sms-gateway/server PR: 178
File: pkg/cache/redis.go:118-121
Timestamp: 2025-09-10T23:54:16.828Z
Learning: Redis 7.4.0 introduced HEXPIRE, HEXPIREAT, and related commands that support per-field TTL on hash data structures. go-redis v9.6.0+ includes support for these commands with methods like HExpireAt.
Applied to files:
pkg/cache/redis.go
🧬 Code graph analysis (47)
internal/sms-gateway/modules/webhooks/repository.go (1)
internal/sms-gateway/modules/webhooks/models.go (1)
Webhook(11-25)
internal/sms-gateway/modules/settings/service.go (1)
internal/sms-gateway/modules/settings/models.go (1)
NewDeviceSettings(19-25)
internal/sms-gateway/handlers/settings/mobile.go (5)
internal/sms-gateway/handlers/base/handler.go (1)
Handler(15-18)internal/sms-gateway/modules/settings/service.go (1)
Service(19-25)internal/sms-gateway/handlers/messages/mobile.go (2)
NewMobileController(35-43)MobileController(29-33)internal/sms-gateway/handlers/webhooks/mobile.go (2)
NewMobileController(21-33)MobileController(15-19)internal/sms-gateway/models/models.go (1)
Device(34-45)
internal/sms-gateway/modules/settings/module.go (3)
internal/sms-gateway/app.go (1)
Module(33-58)internal/sms-gateway/handlers/module.go (1)
Module(15-41)internal/sms-gateway/modules/settings/service.go (1)
NewService(27-35)
internal/sms-gateway/modules/sse/config.go (1)
internal/config/config.go (1)
Config(10-20)
internal/sms-gateway/modules/sse/module.go (1)
internal/sms-gateway/app.go (1)
Module(33-58)
internal/sms-gateway/handlers/devices/3rdparty.go (5)
internal/sms-gateway/handlers/base/handler.go (1)
Handler(15-18)internal/sms-gateway/handlers/messages/3rdparty.go (2)
NewThirdPartyController(44-53)ThirdPartyController(37-42)internal/sms-gateway/handlers/settings/3rdparty.go (2)
NewThirdPartyController(35-44)ThirdPartyController(28-33)internal/sms-gateway/handlers/webhooks/3rdparty.go (2)
NewThirdPartyController(32-40)ThirdPartyController(26-30)internal/sms-gateway/handlers/logs/3rdparty.go (2)
NewThirdPartyController(24-31)ThirdPartyController(20-22)
internal/sms-gateway/modules/metrics/module.go (1)
internal/sms-gateway/app.go (1)
Module(33-58)
internal/sms-gateway/handlers/middlewares/userauth/userauth.go (1)
internal/sms-gateway/models/models.go (1)
User(18-24)
internal/sms-gateway/models/migration.go (1)
internal/sms-gateway/models/models.go (2)
User(18-24)Device(34-45)
internal/sms-gateway/handlers/webhooks/3rdparty.go (4)
internal/sms-gateway/handlers/base/handler.go (1)
Handler(15-18)internal/sms-gateway/modules/webhooks/models.go (1)
Webhook(11-25)internal/sms-gateway/modules/webhooks/errors.go (1)
IsValidationError(34-36)internal/sms-gateway/modules/webhooks/repository_filter.go (1)
WithExtID(7-11)
internal/sms-gateway/modules/push/service.go (4)
internal/sms-gateway/modules/push/fcm/client.go (2)
Client(14-19)New(21-27)internal/sms-gateway/modules/push/upstream/client.go (2)
Client(22-27)New(29-35)internal/sms-gateway/cache/factory.go (1)
Factory(16-18)internal/sms-gateway/modules/push/types.go (1)
Event(17-17)
internal/sms-gateway/handlers/messages/params.go (2)
internal/sms-gateway/modules/devices/repository_filter.go (1)
SelectFilter(9-9)internal/sms-gateway/modules/messages/repository_filter.go (3)
SelectFilter(16-23)SelectOptions(51-62)Order(7-7)
internal/sms-gateway/modules/messages/service.go (5)
internal/sms-gateway/modules/messages/repository_filter.go (3)
Order(7-7)SelectFilter(16-23)SelectOptions(51-62)internal/sms-gateway/modules/messages/models.go (3)
MessageState(129-134)ProcessingState(12-12)MessageRecipient(111-117)internal/sms-gateway/modules/messages/repository.go (1)
ErrMessageNotFound(16-16)internal/sms-gateway/modules/messages/errors.go (2)
ErrNoContent(7-7)ValidationError(10-10)internal/sms-gateway/modules/events/events.go (1)
NewMessageEnqueuedEvent(9-11)
pkg/cache/memory_edge_test.go (1)
pkg/cache/errors.go (2)
ErrKeyExpired(11-11)ErrKeyNotFound(9-9)
internal/sms-gateway/handlers/messages/3rdparty.go (5)
internal/sms-gateway/handlers/settings/3rdparty.go (2)
NewThirdPartyController(35-44)ThirdPartyController(28-33)internal/sms-gateway/handlers/base/handler.go (1)
Handler(15-18)internal/sms-gateway/modules/devices/repository.go (1)
ErrNotFound(14-14)internal/sms-gateway/modules/messages/service.go (1)
EnqueueOptions(22-24)internal/sms-gateway/modules/messages/errors.go (1)
ValidationError(10-10)
internal/sms-gateway/handlers/health.go (1)
pkg/health/types.go (1)
CheckDetail(54-64)
internal/sms-gateway/modules/push/fcm/client.go (3)
internal/sms-gateway/modules/push/fcm/errors.go (1)
ErrInitializationFailed(6-6)internal/sms-gateway/modules/push/upstream/client.go (1)
Client(22-27)internal/sms-gateway/modules/push/types.go (1)
Event(17-17)
internal/sms-gateway/handlers/events/mobile.go (1)
internal/sms-gateway/models/models.go (1)
Device(34-45)
internal/sms-gateway/handlers/module.go (3)
internal/sms-gateway/app.go (1)
Module(33-58)internal/sms-gateway/modules/db/module.go (1)
Module(16-26)internal/sms-gateway/modules/messages/module.go (1)
Module(10-25)
internal/sms-gateway/modules/messages/models.go (1)
internal/sms-gateway/models/models.go (1)
SoftDeletableModel(12-16)
internal/config/module.go (4)
internal/sms-gateway/app.go (1)
Module(33-58)internal/sms-gateway/modules/sse/config.go (4)
Option(5-5)Config(7-9)NewConfig(19-27)WithKeepAlivePeriod(33-41)internal/config/config.go (13)
Config(10-20)Default(89-127)HTTP(27-33)Database(44-55)Gateway(22-25)API(35-38)GatewayModePublic(6-6)OpenAPI(40-42)Messages(75-78)Tasks(63-65)SSE(71-73)Cache(80-82)PubSub(84-87)internal/sms-gateway/handlers/config.go (1)
Config(3-11)
internal/sms-gateway/modules/settings/repository.go (1)
internal/sms-gateway/modules/settings/models.go (1)
DeviceSettings(10-17)
internal/sms-gateway/modules/db/health.go (1)
pkg/health/types.go (2)
Checks(67-67)Provider(69-75)
internal/sms-gateway/modules/devices/service.go (3)
internal/sms-gateway/models/models.go (1)
Device(34-45)internal/sms-gateway/modules/devices/repository_filter.go (4)
SelectFilter(9-9)WithUserID(23-27)WithID(11-15)ActiveWithin(29-33)internal/sms-gateway/modules/devices/repository.go (1)
ErrNotFound(14-14)
internal/sms-gateway/pubsub/pubsub.go (4)
internal/sms-gateway/pubsub/config.go (1)
Config(4-7)pkg/pubsub/options.go (1)
WithBufferSize(15-19)pkg/pubsub/memory.go (1)
NewMemory(24-38)pkg/pubsub/redis.go (2)
NewRedis(42-78)RedisConfig(15-27)
internal/sms-gateway/handlers/logs/3rdparty.go (3)
internal/sms-gateway/handlers/devices/3rdparty.go (2)
NewThirdPartyController(24-36)ThirdPartyController(18-22)internal/sms-gateway/handlers/messages/3rdparty.go (2)
NewThirdPartyController(44-53)ThirdPartyController(37-42)internal/sms-gateway/handlers/webhooks/3rdparty.go (2)
NewThirdPartyController(32-40)ThirdPartyController(26-30)
internal/sms-gateway/modules/sse/service.go (2)
internal/sms-gateway/modules/sse/errors.go (1)
ErrNoConnection(6-6)internal/sms-gateway/modules/sse/metrics.go (2)
ErrorTypeMarshalError(22-22)ErrorTypeWriteFailure(21-21)
pkg/cache/redis.go (4)
pkg/pubsub/redis.go (2)
NewRedis(42-78)RedisConfig(15-27)pkg/cache/errors.go (2)
ErrInvalidConfig(7-7)ErrKeyNotFound(9-9)pkg/cache/options.go (3)
GetOption(49-49)AndDelete(89-93)Option(6-6)pkg/cache/cache.go (1)
Cache(5-40)
internal/sms-gateway/modules/auth/module.go (2)
internal/sms-gateway/app.go (2)
Module(33-58)Run(60-67)internal/sms-gateway/modules/auth/service.go (2)
New(54-69)Service(39-52)
internal/sms-gateway/modules/messages/repository.go (2)
internal/sms-gateway/modules/messages/repository_filter.go (3)
SelectFilter(16-23)SelectOptions(51-62)Order(7-7)internal/sms-gateway/modules/messages/models.go (2)
Message(35-55)ProcessingStatePending(16-16)
internal/sms-gateway/modules/push/upstream/client.go (1)
internal/sms-gateway/modules/push/fcm/client.go (2)
New(21-27)Client(14-19)
internal/sms-gateway/handlers/messages/mobile.go (4)
internal/sms-gateway/handlers/settings/mobile.go (2)
NewMobileController(21-35)MobileController(14-19)internal/sms-gateway/handlers/webhooks/mobile.go (2)
NewMobileController(21-33)MobileController(15-19)internal/sms-gateway/handlers/base/handler.go (1)
Handler(15-18)internal/sms-gateway/models/models.go (1)
Device(34-45)
internal/sms-gateway/modules/push/module.go (3)
internal/sms-gateway/modules/push/fcm/client.go (2)
New(21-27)Client(14-19)internal/sms-gateway/modules/push/service.go (1)
New(43-73)internal/sms-gateway/modules/push/upstream/client.go (2)
New(29-35)Client(22-27)
pkg/cache/memory_concurrency_test.go (1)
pkg/cache/errors.go (2)
ErrKeyNotFound(9-9)ErrKeyExists(13-13)
internal/config/config.go (4)
internal/sms-gateway/modules/sse/config.go (1)
Config(7-9)internal/sms-gateway/modules/messages/config.go (1)
Config(5-8)internal/sms-gateway/pubsub/config.go (1)
Config(4-7)internal/sms-gateway/cache/config.go (1)
Config(4-6)
internal/sms-gateway/modules/auth/service.go (5)
internal/sms-gateway/online/service.go (2)
New(28-37)Service(14-17)internal/sms-gateway/models/models.go (4)
User(18-24)NewUser(26-32)Device(34-45)NewDevice(47-53)internal/sms-gateway/modules/auth/types.go (1)
OneTimeCode(15-18)pkg/crypto/passwords.go (2)
MakeBCryptHash(14-20)CompareBCryptHash(22-27)internal/sms-gateway/modules/auth/errors.go (1)
ErrAuthorizationFailed(6-6)
pkg/cache/memory_bench_test.go (1)
pkg/cache/memory.go (1)
NewMemory(16-23)
internal/sms-gateway/handlers/webhooks/mobile.go (5)
internal/sms-gateway/handlers/base/handler.go (1)
Handler(15-18)internal/sms-gateway/modules/webhooks/service.go (1)
Service(28-37)internal/sms-gateway/handlers/messages/mobile.go (2)
NewMobileController(35-43)MobileController(29-33)internal/sms-gateway/handlers/settings/mobile.go (2)
NewMobileController(21-35)MobileController(14-19)internal/sms-gateway/modules/webhooks/repository_filter.go (1)
WithDeviceID(41-46)
internal/sms-gateway/modules/auth/repository.go (1)
internal/sms-gateway/models/models.go (1)
User(18-24)
pkg/cache/memory_profile_test.go (1)
pkg/cache/memory.go (1)
NewMemory(16-23)
internal/sms-gateway/modules/events/module.go (1)
internal/sms-gateway/app.go (2)
Module(33-58)Run(60-67)
internal/sms-gateway/modules/push/types.go (2)
internal/sms-gateway/modules/events/types.go (1)
Event(10-13)internal/sms-gateway/modules/sse/types.go (1)
Event(7-10)
internal/sms-gateway/modules/webhooks/module.go (3)
internal/sms-gateway/app.go (1)
Module(33-58)internal/sms-gateway/modules/webhooks/repository.go (1)
NewRepository(12-16)internal/sms-gateway/modules/webhooks/service.go (1)
NewService(39-50)
internal/sms-gateway/app.go (4)
internal/config/module.go (1)
Module(23-140)internal/sms-gateway/pubsub/module.go (1)
Module(11-30)internal/sms-gateway/handlers/module.go (1)
Module(15-41)internal/sms-gateway/modules/db/module.go (1)
Module(16-26)
internal/sms-gateway/handlers/mobile.go (1)
internal/sms-gateway/models/models.go (2)
Device(34-45)User(18-24)
internal/sms-gateway/modules/webhooks/models.go (2)
internal/sms-gateway/models/models.go (3)
SoftDeletableModel(12-16)User(18-24)Device(34-45)internal/sms-gateway/modules/settings/models.go (1)
Migrate(27-32)
🪛 checkmake (0.2.2)
Makefile
[warning] 10-10: Missing required phony target "all"
(minphony)
[warning] 10-10: Missing required phony target "clean"
(minphony)
[warning] 10-10: Missing required phony target "test"
(minphony)
[warning] 15-15: Target "all" should be declared PHONY.
(phonydeclared)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (4)
- GitHub Check: Benchmark
- GitHub Check: Test
- GitHub Check: E2E
- GitHub Check: Analyze (go)
004675f to
ebd5efe
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 0
♻️ Duplicate comments (6)
internal/sms-gateway/modules/push/upstream/client.go (1)
62-62: Remove the unnecessary type conversion.As previously noted,
UpstreamPushRequestis a type alias for[]smsgateway.PushNotification, making the explicit conversion redundant. Remove it:- payloadBytes, err := json.Marshal(smsgateway.UpstreamPushRequest(payload)) //nolint:unconvert //type checking + payloadBytes, err := json.Marshal(payload)internal/sms-gateway/handlers/mobile.go (1)
275-276: Previous review concern remains unaddressed.While the named constant
idGenSizeis a good improvement, the error fromnanoid.Standard(idGenSize)is still being silently ignored. The previous review flagged this and suggested either handling the error or documenting why it's safe to ignore. Neither approach was implemented.Please apply one of the previously suggested solutions:
Option 1: Handle the error (recommended):
const idGenSize = 21 - idGen, _ := nanoid.Standard(idGenSize) + idGen, err := nanoid.Standard(idGenSize) + if err != nil { + params.Logger.Fatal("failed to initialize ID generator", zap.Error(err)) + }Option 2: Document why it's safe:
const idGenSize = 21 + // nanoid.Standard with size 21 never returns an error idGen, _ := nanoid.Standard(idGenSize)internal/sms-gateway/modules/auth/service.go (2)
55-56: Document or handle the ignored error.The error from
nanoid.Standard(idLen)is still being silently discarded. While21is a valid size, consider adding a comment explaining why it's safe to ignore, as previously suggested.
218-218: Critical: Cache invalidation key mismatch.Line 218 constructs the cache key as
sha256(userID + currentPassword)without the"\x00"separator, butAuthorizeUserat line 160 usessha256(username + "\x00" + password). This mismatch meansChangePasswordwill never successfully invalidate the cached user entry, allowing authentication with the old password until the 1-hour cache TTL expires.Apply this diff to fix the cache key construction:
- hash := sha256.Sum256([]byte(userID + currentPassword)) + hash := sha256.Sum256([]byte(userID + "\x00" + currentPassword))internal/config/module.go (1)
90-92: Don't override API path when only host is omitted.This condition resets
cfg.HTTP.API.Pathto/apiwhenever the host is blank, clobbering any explicit path users configured while still relying on the implicit host. Key off the path field itself so user-supplied values survive.Apply this diff:
- if cfg.HTTP.API.Host == "" { + if cfg.HTTP.API.Path == "" { cfg.HTTP.API.Path = "/api" }internal/sms-gateway/modules/messages/service.go (1)
276-279: Use explicitProcessingStatePendinginstead of empty string.Line 278 passes an empty string
""as the state parameter tonewMessageRecipient, which is not a validProcessingStateenum value. For type safety and clarity, explicitly useProcessingStatePending.Apply this diff:
func (s *Service) recipientsToModel(input []string) []MessageRecipient { return lo.Map( input, - func(item string, _ int) MessageRecipient { return newMessageRecipient(item, "", nil) }, + func(item string, _ int) MessageRecipient { return newMessageRecipient(item, ProcessingStatePending, nil) }, ) }
🧹 Nitpick comments (9)
internal/sms-gateway/modules/push/upstream/client.go (1)
29-35: Consider removing redundant zero-value initialization.Lines 32-33 explicitly initialize fields to their zero values (
client: nilandmux: sync.Mutex{}), which Go does automatically. You can simplify:func New(options map[string]string) (*Client, error) { return &Client{ options: options, - client: nil, - mux: sync.Mutex{}, }, nil }internal/sms-gateway/pubsub/pubsub.go (1)
35-35: Simplify the assignment pattern.The pattern
pubSub, err = pubsub.NewMemory(opts...), nilis unconventional and less readable. SinceNewMemoryreturns only a single value (no error), consider simplifying to:- pubSub, err = pubsub.NewMemory(opts...), nil + pubSub = pubsub.NewMemory(opts...) + err = nilOr simply:
- pubSub, err = pubsub.NewMemory(opts...), nil + pubSub = pubsub.NewMemory(opts...)(The
errvariable will remainnilfrom its declaration on line 32.)pkg/pubsub/redis.go (1)
73-74: Consider removing redundant zero-value initialization.The explicit initialization of
wgandmuwith their zero values is redundant in Go. These fields will be zero-initialized by default. Removing these lines would simplify the code without changing behavior:return &RedisPubSub{ prefix: config.Prefix, bufferSize: o.bufferSize, client: client, ownedClient: config.Client == nil, - wg: sync.WaitGroup{}, - mu: sync.Mutex{}, subscribers: make(map[string]context.CancelFunc), closeCh: make(chan struct{}), }, nilpkg/cache/redis.go (2)
136-137: Good refactor: Named constant improves clarity.Using
itemsPerKeymakes it explicit that Redis HGETALL returns key-value pairs, eliminating the magic number2from the capacity calculation.
185-186: Minor: Consider keeping the chained call on one line.While the line split is valid, keeping the entire expression on one line may improve readability:
- result, err := r.client.Eval(ctx, getAndUpdateTTLScript, []string{r.key}, key, delArg, ttlTimestamp, ttlDelta). - Result() + result, err := r.client.Eval(ctx, getAndUpdateTTLScript, []string{r.key}, key, delArg, ttlTimestamp, ttlDelta).Result()internal/config/module.go (2)
62-64: Remove redundant zero-value assignments.Explicitly assigning zero values (
DSN: "",ConnMaxIdleTime: 0,ConnMaxLifetime: 0) is unnecessary in Go; omitted fields default to their zero values automatically.Apply this diff:
MaxOpenConns: cfg.Database.MaxOpenConns, MaxIdleConns: cfg.Database.MaxIdleConns, - - DSN: "", - ConnMaxIdleTime: 0, - ConnMaxLifetime: 0, }
118-122: Address the TODO: make UnusedLifetime configurable.The hardcoded 365-day unused lifetime should be exposed as a configuration field for flexibility.
Would you like me to generate the configuration wiring to make
UnusedLifetimeconfigurable, or open a new issue to track this task?internal/sms-gateway/handlers/upstream.go (1)
97-100: LGTM: Constants improve code clarity.Extracting the rate limit values to named constants improves readability and maintainability.
Consider making these configurable via the
Configstruct if rate limiting needs may vary across deployments.internal/sms-gateway/modules/sse/service.go (1)
46-46: Optional: Explicit zero-value initialization is redundant.The explicit
sync.RWMutex{}initialization is unnecessary since Go automatically zero-initializes struct fields. You can simplify to justmu: sync.RWMutex{}or remove the line entirely (relying on zero-value).
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (50)
.github/workflows/release.yml(2 hunks)Dockerfile.goreleaser(1 hunks)Makefile(1 hunks)internal/config/config.go(3 hunks)internal/config/module.go(1 hunks)internal/sms-gateway/cache/factory.go(2 hunks)internal/sms-gateway/handlers/base/handler.go(1 hunks)internal/sms-gateway/handlers/devices/3rdparty.go(4 hunks)internal/sms-gateway/handlers/messages/3rdparty.go(8 hunks)internal/sms-gateway/handlers/messages/mobile.go(3 hunks)internal/sms-gateway/handlers/middlewares/userauth/userauth.go(2 hunks)internal/sms-gateway/handlers/mobile.go(8 hunks)internal/sms-gateway/handlers/root.go(1 hunks)internal/sms-gateway/handlers/settings/3rdparty.go(5 hunks)internal/sms-gateway/handlers/settings/mobile.go(2 hunks)internal/sms-gateway/handlers/upstream.go(4 hunks)internal/sms-gateway/handlers/webhooks/3rdparty.go(4 hunks)internal/sms-gateway/handlers/webhooks/mobile.go(2 hunks)internal/sms-gateway/modules/auth/service.go(6 hunks)internal/sms-gateway/modules/devices/service.go(6 hunks)internal/sms-gateway/modules/events/service.go(7 hunks)internal/sms-gateway/modules/events/types.go(2 hunks)internal/sms-gateway/modules/messages/cache.go(2 hunks)internal/sms-gateway/modules/messages/converters.go(1 hunks)internal/sms-gateway/modules/messages/errors.go(1 hunks)internal/sms-gateway/modules/messages/repository.go(5 hunks)internal/sms-gateway/modules/messages/service.go(11 hunks)internal/sms-gateway/modules/messages/workers.go(1 hunks)internal/sms-gateway/modules/push/fcm/client.go(4 hunks)internal/sms-gateway/modules/push/fcm/utils.go(1 hunks)internal/sms-gateway/modules/push/service.go(10 hunks)internal/sms-gateway/modules/push/types.go(3 hunks)internal/sms-gateway/modules/push/upstream/client.go(4 hunks)internal/sms-gateway/modules/settings/service.go(3 hunks)internal/sms-gateway/modules/sse/service.go(8 hunks)internal/sms-gateway/modules/webhooks/service.go(5 hunks)internal/sms-gateway/online/service.go(5 hunks)internal/sms-gateway/pubsub/module.go(2 hunks)internal/sms-gateway/pubsub/pubsub.go(2 hunks)internal/worker/app.go(1 hunks)internal/worker/config/config.go(1 hunks)internal/worker/config/types.go(2 hunks)internal/worker/executor/metrics.go(1 hunks)internal/worker/executor/service.go(3 hunks)pkg/cache/memory_bench_test.go(19 hunks)pkg/cache/memory_concurrency_test.go(12 hunks)pkg/cache/redis.go(12 hunks)pkg/crypto/passwords.go(1 hunks)pkg/mysql/errors.go(1 hunks)pkg/pubsub/redis.go(7 hunks)
🚧 Files skipped from review as they are similar to previous changes (16)
- Dockerfile.goreleaser
- internal/sms-gateway/handlers/root.go
- pkg/cache/memory_concurrency_test.go
- internal/sms-gateway/modules/push/fcm/utils.go
- pkg/mysql/errors.go
- internal/worker/executor/metrics.go
- internal/sms-gateway/handlers/devices/3rdparty.go
- internal/sms-gateway/modules/push/fcm/client.go
- internal/worker/executor/service.go
- internal/worker/config/config.go
- internal/sms-gateway/pubsub/module.go
- internal/sms-gateway/handlers/webhooks/mobile.go
- internal/worker/app.go
- internal/sms-gateway/modules/webhooks/service.go
- internal/sms-gateway/modules/events/service.go
- internal/sms-gateway/modules/push/service.go
🧰 Additional context used
🧠 Learnings (6)
📚 Learning: 2025-09-10T23:45:45.502Z
Learnt from: capcom6
Repo: android-sms-gateway/server PR: 178
File: internal/sms-gateway/online/module.go:14-16
Timestamp: 2025-09-10T23:45:45.502Z
Learning: fx.Decorate in uber-go/fx creates module-scoped decorated dependencies, not global modifications. Using fx.Decorate(func(log *zap.Logger) *zap.Logger { return log.Named("module_name") }) is a correct pattern for creating module-specific loggers that are scoped only to that module's dependency graph.
Applied to files:
internal/config/module.go
📚 Learning: 2025-09-10T23:53:42.006Z
Learnt from: capcom6
Repo: android-sms-gateway/server PR: 178
File: pkg/cache/redis.go:63-75
Timestamp: 2025-09-10T23:53:42.006Z
Learning: In pkg/cache/redis.go, the Redis cache implementation uses Redis's native field-level TTL via HExpireAt, meaning expired fields are automatically removed by Redis itself. This differs from the memory implementation where the application tracks expiration times. As a result, operations like Drain that call HGetAll will only return non-expired items since expired fields are already removed by Redis.
Applied to files:
pkg/cache/redis.gointernal/sms-gateway/online/service.go
📚 Learning: 2025-09-10T23:54:16.828Z
Learnt from: capcom6
Repo: android-sms-gateway/server PR: 178
File: pkg/cache/redis.go:118-121
Timestamp: 2025-09-10T23:54:16.828Z
Learning: Redis 7.4.0 introduced HEXPIRE, HEXPIREAT, and related commands that support per-field TTL on hash data structures. go-redis v9.6.0+ includes support for these commands with methods like HExpireAt.
Applied to files:
pkg/cache/redis.go
📚 Learning: 2025-09-10T11:11:43.462Z
Learnt from: capcom6
Repo: android-sms-gateway/server PR: 178
File: pkg/cache/memory.go:59-74
Timestamp: 2025-09-10T11:11:43.462Z
Learning: In pkg/cache/memory.go, the cleanup method removes expired items first, then executes the provided callback function. This means methods like Drain that use cleanup don't need additional expiration filtering since expired items are already removed before the callback runs.
Applied to files:
pkg/cache/redis.gopkg/cache/memory_bench_test.gointernal/sms-gateway/online/service.go
📚 Learning: 2025-07-22T00:31:28.796Z
Learnt from: capcom6
Repo: android-sms-gateway/server PR: 154
File: internal/sms-gateway/modules/sse/service.go:67-79
Timestamp: 2025-07-22T00:31:28.796Z
Learning: In the SSE service Send method (internal/sms-gateway/modules/sse/service.go), blocking on full channels is intentional and acceptable. Timeout control is handled at the calling code level through context, not within the Send method itself. The user prefers this approach over adding a default case that would drop messages when channels are full.
Applied to files:
internal/sms-gateway/modules/sse/service.go
📚 Learning: 2025-07-22T00:34:31.896Z
Learnt from: capcom6
Repo: android-sms-gateway/server PR: 154
File: internal/sms-gateway/modules/sse/service.go:89-92
Timestamp: 2025-07-22T00:34:31.896Z
Learning: In Go SSE implementations, when cleaning up connections with buffered channels, avoid closing the data channels directly as this can cause panics if concurrent goroutines attempt to write to them. Instead, use a separate close signal channel to notify writers that the connection should be terminated, allowing the data channels to be garbage collected naturally when the connection is removed.
Applied to files:
internal/sms-gateway/modules/sse/service.go
🧬 Code graph analysis (24)
internal/sms-gateway/modules/messages/cache.go (2)
internal/sms-gateway/modules/messages/domain.go (1)
MessageStateOut(38-44)pkg/cache/options.go (2)
WithTTL(20-31)AndSetTTL(65-69)
internal/sms-gateway/handlers/middlewares/userauth/userauth.go (1)
internal/sms-gateway/models/models.go (1)
User(18-24)
internal/sms-gateway/pubsub/pubsub.go (4)
internal/sms-gateway/pubsub/config.go (1)
Config(4-7)pkg/pubsub/options.go (1)
WithBufferSize(15-19)pkg/pubsub/memory.go (1)
NewMemory(24-38)pkg/pubsub/redis.go (2)
NewRedis(42-78)RedisConfig(15-27)
internal/config/module.go (7)
internal/sms-gateway/app.go (1)
Module(33-58)internal/sms-gateway/modules/sse/config.go (4)
Option(5-5)Config(7-9)NewConfig(19-27)WithKeepAlivePeriod(33-41)internal/config/config.go (13)
Config(10-20)Default(89-127)HTTP(27-33)Database(44-55)Gateway(22-25)API(35-38)GatewayModePublic(6-6)OpenAPI(40-42)Messages(75-78)Tasks(63-65)SSE(71-73)Cache(80-82)PubSub(84-87)internal/sms-gateway/modules/messages/config.go (1)
Config(5-8)internal/sms-gateway/pubsub/config.go (1)
Config(4-7)internal/sms-gateway/cache/config.go (1)
Config(4-6)internal/sms-gateway/handlers/config.go (1)
Config(3-11)
internal/sms-gateway/modules/push/types.go (2)
internal/sms-gateway/modules/events/types.go (1)
Event(10-13)internal/sms-gateway/modules/sse/types.go (1)
Event(7-10)
internal/sms-gateway/handlers/settings/3rdparty.go (5)
internal/sms-gateway/handlers/devices/3rdparty.go (2)
NewThirdPartyController(24-36)ThirdPartyController(18-22)internal/sms-gateway/handlers/messages/3rdparty.go (2)
NewThirdPartyController(44-53)ThirdPartyController(37-42)internal/sms-gateway/handlers/webhooks/3rdparty.go (2)
NewThirdPartyController(32-40)ThirdPartyController(26-30)internal/sms-gateway/handlers/base/handler.go (1)
Handler(15-18)internal/sms-gateway/modules/settings/models.go (1)
DeviceSettings(10-17)
internal/sms-gateway/handlers/messages/3rdparty.go (6)
internal/sms-gateway/handlers/base/handler.go (1)
Handler(15-18)internal/sms-gateway/models/models.go (1)
User(18-24)internal/sms-gateway/modules/devices/repository.go (1)
ErrNotFound(14-14)internal/sms-gateway/modules/messages/service.go (1)
EnqueueOptions(22-24)internal/sms-gateway/modules/messages/errors.go (1)
ValidationError(10-10)internal/sms-gateway/modules/messages/repository.go (1)
ErrMessageAlreadyExists(17-17)
internal/sms-gateway/handlers/webhooks/3rdparty.go (5)
internal/sms-gateway/handlers/messages/3rdparty.go (2)
NewThirdPartyController(44-53)ThirdPartyController(37-42)internal/sms-gateway/handlers/base/handler.go (1)
Handler(15-18)internal/sms-gateway/modules/webhooks/models.go (1)
Webhook(11-25)internal/sms-gateway/modules/webhooks/errors.go (1)
IsValidationError(34-36)internal/sms-gateway/modules/webhooks/repository_filter.go (1)
WithExtID(7-11)
internal/sms-gateway/modules/push/upstream/client.go (2)
internal/sms-gateway/modules/push/fcm/client.go (2)
New(21-27)Client(14-19)internal/sms-gateway/modules/push/service.go (1)
New(43-73)
pkg/pubsub/redis.go (3)
pkg/cache/redis.go (2)
NewRedis(81-108)RedisConfig(56-70)pkg/pubsub/options.go (1)
Option(3-3)pkg/pubsub/pubsub.go (3)
ErrInvalidConfig(9-9)Subscription(19-24)PubSub(46-51)
internal/sms-gateway/handlers/settings/mobile.go (4)
internal/sms-gateway/handlers/base/handler.go (1)
Handler(15-18)internal/sms-gateway/handlers/messages/mobile.go (2)
NewMobileController(35-43)MobileController(29-33)internal/sms-gateway/handlers/webhooks/mobile.go (2)
NewMobileController(21-33)MobileController(15-19)internal/sms-gateway/models/models.go (1)
Device(34-45)
internal/sms-gateway/cache/factory.go (2)
pkg/cache/errors.go (1)
ErrInvalidConfig(7-7)internal/config/config.go (1)
Cache(80-82)
internal/config/config.go (6)
internal/worker/config/config.go (3)
Default(34-66)Config(9-13)Tasks(15-19)internal/sms-gateway/modules/push/service.go (1)
Config(23-30)internal/sms-gateway/modules/sse/config.go (1)
Config(7-9)internal/sms-gateway/modules/messages/config.go (1)
Config(5-8)internal/sms-gateway/pubsub/config.go (1)
Config(4-7)internal/sms-gateway/cache/config.go (1)
Config(4-6)
pkg/cache/redis.go (4)
pkg/pubsub/redis.go (2)
NewRedis(42-78)RedisConfig(15-27)pkg/cache/errors.go (2)
ErrInvalidConfig(7-7)ErrKeyNotFound(9-9)pkg/cache/options.go (3)
GetOption(49-49)AndDelete(89-93)Option(6-6)pkg/cache/cache.go (1)
Cache(5-40)
internal/sms-gateway/handlers/messages/mobile.go (4)
internal/sms-gateway/handlers/settings/mobile.go (2)
NewMobileController(21-35)MobileController(14-19)internal/sms-gateway/handlers/webhooks/mobile.go (2)
NewMobileController(21-33)MobileController(15-19)internal/sms-gateway/handlers/base/handler.go (1)
Handler(15-18)internal/sms-gateway/models/models.go (1)
Device(34-45)
internal/sms-gateway/modules/messages/service.go (4)
internal/sms-gateway/modules/messages/repository_filter.go (3)
Order(7-7)SelectFilter(16-23)SelectOptions(51-62)internal/sms-gateway/modules/messages/models.go (3)
MessageState(129-134)ProcessingState(12-12)MessageRecipient(111-117)internal/sms-gateway/modules/messages/errors.go (2)
ErrNoContent(7-7)ValidationError(10-10)internal/sms-gateway/modules/events/events.go (1)
NewMessageEnqueuedEvent(9-11)
pkg/cache/memory_bench_test.go (1)
pkg/cache/memory.go (1)
NewMemory(16-23)
internal/sms-gateway/modules/settings/service.go (1)
internal/sms-gateway/modules/settings/models.go (1)
NewDeviceSettings(19-25)
internal/sms-gateway/modules/messages/converters.go (1)
internal/sms-gateway/modules/messages/domain.go (1)
MessageOut(25-29)
internal/sms-gateway/modules/auth/service.go (6)
internal/sms-gateway/online/service.go (2)
New(28-37)Service(14-17)internal/sms-gateway/handlers/middlewares/deviceauth/deviceauth.go (1)
New(20-44)internal/sms-gateway/models/models.go (4)
User(18-24)NewUser(26-32)Device(34-45)NewDevice(47-53)internal/sms-gateway/modules/auth/types.go (1)
OneTimeCode(15-18)pkg/crypto/passwords.go (2)
MakeBCryptHash(14-20)CompareBCryptHash(22-27)internal/sms-gateway/modules/auth/errors.go (1)
ErrAuthorizationFailed(6-6)
internal/sms-gateway/modules/sse/service.go (2)
internal/sms-gateway/modules/sse/errors.go (1)
ErrNoConnection(6-6)internal/sms-gateway/modules/sse/metrics.go (2)
ErrorTypeMarshalError(22-22)ErrorTypeWriteFailure(21-21)
internal/sms-gateway/handlers/mobile.go (1)
internal/sms-gateway/models/models.go (2)
Device(34-45)User(18-24)
internal/sms-gateway/modules/devices/service.go (3)
internal/sms-gateway/models/models.go (1)
Device(34-45)internal/sms-gateway/modules/devices/repository_filter.go (4)
SelectFilter(9-9)WithUserID(23-27)WithID(11-15)ActiveWithin(29-33)internal/sms-gateway/modules/devices/repository.go (1)
ErrNotFound(14-14)
internal/sms-gateway/modules/messages/repository.go (2)
internal/sms-gateway/modules/messages/repository_filter.go (3)
SelectFilter(16-23)SelectOptions(51-62)Order(7-7)internal/sms-gateway/modules/messages/models.go (2)
Message(35-55)ProcessingStatePending(16-16)
🪛 checkmake (0.2.2)
Makefile
[warning] 10-10: Missing required phony target "all"
(minphony)
[warning] 10-10: Missing required phony target "clean"
(minphony)
[warning] 10-10: Missing required phony target "test"
(minphony)
[warning] 15-15: Target "all" should be declared PHONY.
(phonydeclared)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (4)
- GitHub Check: Test
- GitHub Check: Benchmark
- GitHub Check: E2E
- GitHub Check: Analyze (go)
🔇 Additional comments (72)
pkg/crypto/passwords.go (1)
17-17: LGTM! Error message standardization improves consistency.The updated error message follows the "failed to ..." pattern being standardized across the project. The error wrapping remains correct, and there's no impact on functionality.
internal/worker/config/types.go (1)
16-16: LGTM! Error message standardization improves consistency.The error messages have been standardized to use "failed to" instead of "can't", which is more formal and consistent with error message conventions. Error wrapping with
%wis correctly preserved in all cases.Also applies to: 25-25, 30-30
internal/sms-gateway/modules/messages/converters.go (2)
13-13: LGTM: Idiomatic Go variable declaration.Using
var ttl *uint64without explicit nil initialization is the idiomatic approach in Go, leveraging zero-value initialization.
21-21: LGTM: Improved error message consistency.The error messages now use "failed to" instead of "can't", which is more consistent with professional Go error conventions.
Also applies to: 25-25
internal/sms-gateway/modules/events/types.go (2)
28-35: LGTM! Proper error wrapping added.The error handling now uses
fmt.Errorfwith the%wverb for proper error wrapping, allowing the error chain to be inspected witherrors.Isorerrors.As. The error message is clear and descriptive.
37-43: LGTM! Consistent error handling.The error handling is consistent with the
serialize()method, using proper error wrapping with%wand a descriptive error message.Makefile (5)
2-3: Well-organized registry and image variables for GoReleaser integration.The addition of
registry_nameandimage_namecentralizes Docker registry configuration, making it easier to maintain and align with the GoReleaser-based build pipeline.
10-13: Proper multiline .PHONY declaration.The
.PHONYlist has been properly expanded with backslash continuation. All targets (all, clean, test, etc.) are correctly declared and defined in the file. Static analysis warnings about missing PHONY targets are false positives—checkmake struggles with multiline PHONY declarations but the syntax is valid and complete.
26-28: Coverage target refactored with explicit test dependency.The
coveragetarget now depends ontest, ensuring test runs before coverage report generation. This separation is cleaner and more maintainable.
39-44: Clean and help targets well-implemented.The
cleantarget now removes coverage artifacts and dist directory (GoReleaser output), and the newhelptarget provides user-friendly target documentation. Both implementations are professional and follow Makefile conventions.
36-37: Goreleaser DOCKER_REGISTRY integration is properly configured.The
.goreleaser.yamlcorrectly references theDOCKER_REGISTRYenvironment variable passed from the Makefile'sreleasetarget. The configuration uses proper goreleaser template syntax ({{ .Env.DOCKER_REGISTRY }}) in both the image name and OCI labels, ensuring the registry environment variable is properly consumed for image naming and registry selection.internal/sms-gateway/handlers/middlewares/userauth/userauth.go (3)
90-90: LGTM: Clean delegation to GetUser.The simplified implementation correctly delegates to
GetUser(c) != nil, avoiding code duplication and maintaining consistency with the new pointer-returning signature.
94-100: LGTM: Safe pointer return with nil handling.The signature change to return
*models.Userwith nil-safe type assertion prevents panics when the user is not present or the Locals value has an unexpected type. The implementation correctly matches the pointer type stored byNewBasicandNewCodemiddlewares.
118-123: LGTM: Proper nil guard before dereference.The explicit nil check at line 119 prevents panics when dereferencing the user pointer at line 123. The clear error message and defensive structure align with the nil-safety improvements mentioned in the past review.
pkg/cache/memory_bench_test.go (2)
14-446: LGTM: Documentation consistency improved.The addition of trailing periods to all benchmark function comments improves consistency and professionalism in the documentation.
125-125: Go version verified; code changes are appropriate.The project requires Go 1.24.3 (per
go.mod), which exceeds the minimum Go 1.22 requirement for range-based integer loops. The loop syntax conversions are valid and the code is ready.internal/sms-gateway/modules/push/upstream/client.go (2)
18-20: LGTM! Good addition of sentinel error.The
ErrInvalidResponsesentinel error follows Go best practices and enables callers to check for specific error conditions usingerrors.Is().
50-94: LGTM! Excellent error handling improvements.The error handling is well-structured:
- Proper error wrapping with
%wthroughoutmapErrorshelper provides consistent error propagation for batch failures- Sentinel error
ErrInvalidResponseis appropriately used with contextAlso applies to: 96-103
internal/sms-gateway/modules/push/types.go (3)
5-5: LGTM: Clean refactoring of imports and type alias.The import changes and type alias refactoring align well with the broader module reorganization described in the PR summary. The
fmtimport supports the improved error wrapping, and the switch from internal types to the client package is consistent with the push client abstraction.Also applies to: 7-7, 17-17
19-23: LGTM: Exported fields are correct for JSON serialization.The
EventandRetriesfields are properly exported (capitalized) to enable JSON marshaling/unmarshaling via the struct tags. This is the correct approach for theserialize()anddeserialize()methods.
29-36: LGTM: Improved error handling with proper wrapping.The error wrapping using
fmt.Errorfwith%wprovides better error context while preserving the original error for unwrapping. This follows Go best practices and improves debuggability.Also applies to: 38-44
.github/workflows/release.yml (5)
16-19: Checkout action versions are now consistent.Both the goreleaser job (line 17) and deploy job (line 67) now use
actions/checkout@v5, resolving the previous version mismatch. ✓Also applies to: 67-67
63-64: APP_VERSION extraction looks correct.The
${GITHUB_REF#refs/tags/}parameter expansion correctly extracts the version tag (e.g.,v1.2.3fromrefs/tags/v1.2.3). Combined with the tag-based trigger, this reliably populates the version for downstream steps.
21-46: GoReleaser setup is well-configured.The job correctly:
- Uses
fetch-depth: 0for full history (needed for changelog generation)- Authenticates to GHCR via
docker/login-action- Sets up Docker Buildx and Go dependencies
- Passes
GITHUB_TOKENandDOCKER_REGISTRYto GoReleaser- Uses action version ~> v2 (pinned to major version)
49-114: Deploy jobs correctly depend on goreleaser.Both
deploy(line 56) anddeploy-secondary(line 99) now depend on the goreleaser job, establishing the correct execution order. Thedeployjob extracts the version viaAPP_VERSIONand uses it in Terraform variables (line 88).
35-36: Let me search the workflow file itself to see if RELEASE_ID is actually used anywhere:Based on my verification, the
RELEASE_IDenvironment variable on line 36 is indeed unused—it does not appear anywhere in the repository's YAML configuration files or elsewhere in the workflow. The calculation itself is syntactically correct for GitHub Actions, but its purpose is undocumented and unclear.Remove or document the RELEASE_ID calculation.
Line 36 computes
RELEASE_IDas days since 2022-06-15, but it is not referenced in.goreleaser.yaml, the workflow steps, or elsewhere in the codebase. Either remove this dead code or add a comment explaining its intended purpose (e.g., if it's a placeholder for future versioning logic).internal/sms-gateway/pubsub/pubsub.go (2)
4-4: LGTM: Sentinel error addition follows best practices.The introduction of
ErrInvalidSchemeas an exported sentinel error enables callers to perform typed error checking and is consistent with error handling patterns used in other modules (e.g.,ErrInvalidConfigin cache and pubsub packages).Also applies to: 17-17
32-50: LGTM: Well-structured error handling refactor.The refactoring to use a temporary variable (line 32) with deferred error checking (lines 46-48) improves code organization and ensures consistent error wrapping across all constructor paths.
pkg/pubsub/redis.go (2)
29-29: LGTM: Consistent type export to public API.The rename from
redisPubSubtoRedisPubSubmakes this type part of the public API. All references (constructor, methods, assertion) have been updated consistently. This aligns with the broader refactoring mentioned in the PR objectives.Also applies to: 42-42, 66-66, 80-80, 98-98, 169-169, 188-188
48-48: LGTM: Consistent and proper error wrapping.All error wrapping throughout the file follows best practices:
- Uses
%wfor error chain preservation- Wraps configuration errors with
ErrInvalidConfigsentinel- Provides descriptive error messages for debugging
Also applies to: 91-93, 113-113, 180-182
internal/sms-gateway/handlers/mobile.go (4)
65-69: LGTM! Minor clarity improvements.The explicit
Device: nilinitialization and comment punctuation improve code clarity without changing behavior.
95-101: LGTM! Improved function signature and error handling.The removal of the named return parameter and the use of
fiber.StatusBadRequestfor body parsing errors are good improvements. The previous issue with incorrect HTTP status has been properly addressed.
104-127: LGTM! Consistent error messaging.The updated error messages follow the "failed to..." pattern consistently used throughout the codebase, improving error clarity and maintainability.
236-243: LGTM! Improved parameter handling.Using
_for the unused context parameter is idiomatic Go and makes it clear the parameter is intentionally not used.pkg/cache/redis.go (4)
44-46: LGTM: TTL clamping prevents negative value issues.The defensive check ensures that negative TTL values (e.g., -1 for no expiry, -2 for non-existent field) are clamped to 0 before computing the new TTL with
ttlDelta. This prevents incorrect expiration times.
81-108: LGTM: Constructor improvements align with best practices.The constructor now returns an error, validates configuration properly, and wraps errors with
%wfor better error chains. The pattern is consistent with the pubsub Redis implementation.
168-177: LGTM: Switch refactor correctly handles all option combinations.The condition-based switch improves readability and correctly computes TTL values. Since the atomic script is executed unconditionally after the switch, the delete flag is honored even when no TTL options are set (addressing the previous review concern).
251-259: LGTM: Close method properly wraps errors.The method correctly closes only owned clients and wraps any errors with
%wfor proper error chain propagation.internal/config/config.go (2)
85-86: LGTM! BufferSize field properly integrated.The new
BufferSizefield is correctly added to thePubSubstruct and aligns with thepubsub.Configstructure shown in the relevant snippets.
89-127: Default configuration values are sensible.The
Default()function provides reasonable defaults for all configuration sections. The 15-minute hashing interval (line 109) and 128-buffer PubSub size (line 124) are appropriate starting values.internal/config/module.go (2)
23-140: Module() function follows project patterns correctly.The shift from a module variable to a
Module()function aligns with the broader project pattern demonstrated ininternal/sms-gateway/app.goand enables config-driven wiring.
27-37: The review comment is based on outdated information and can be dismissed.fx.Private is a documented annotation in Uber's fx library that makes a constructor's provided values visible only inside the enclosing fx.Module. The pattern
fx.Provide(constructor, fx.Private)used at line 36 ofinternal/config/module.gois valid and requires no wrapping withfx.Annotate.Codebase verification shows this pattern is consistently used across 16+ modules without evidence of startup errors. The prior concern about this pattern causing issues appears to be incorrect.
Likely an incorrect or invalid review comment.
internal/sms-gateway/modules/messages/workers.go (1)
70-70: LGTM: Consistent error message phrasing.The updated log message aligns with the PR-wide standardization of error messages.
internal/sms-gateway/modules/settings/service.go (2)
56-56: LGTM: Constructor pattern improves maintainability.Using
NewDeviceSettingsinstead of struct literals provides a consistent construction point and allows for future enhancements without breaking existing code.
86-86: LGTM: Consistent error message phrasing.The updated log message aligns with the PR-wide standardization.
internal/sms-gateway/cache/factory.go (3)
31-31: LGTM: Proper error wrapping with sentinel error.Wrapping with
ErrInvalidConfigenables callers to useerrors.Isfor error classification.
37-39: LGTM: Intentional parameter ignore.The memory cache implementation doesn't need namespacing, so ignoring the
nameparameter is appropriate.
53-53: LGTM: Consistent error wrapping.Properly wraps invalid scheme errors with
ErrInvalidConfigsentinel.internal/sms-gateway/handlers/base/handler.go (1)
22-22: LGTM: Consistent error message phrasing.The updated error messages across all parser validators align with the PR-wide standardization.
Also applies to: 30-30, 38-38
internal/sms-gateway/online/service.go (1)
50-50: LGTM: Consistent error message phrasing.All error and log messages have been updated to use consistent "failed to" phrasing.
Also applies to: 65-65, 85-85, 101-101, 111-111
internal/sms-gateway/modules/messages/errors.go (2)
5-8: LGTM: Sentinel errors improve error handling.The new sentinel errors
ErrLockFailedandErrNoContentenable proper error classification witherrors.Is.
10-14: LGTM: ValidationError type provides better error handling.Replacing the string-based
ErrValidationwith a customValidationErrortype improves type safety while maintaining backward compatibility through theError()method.internal/sms-gateway/modules/devices/service.go (3)
80-107: LGTM: Well-structured device selection with fallback logic.The
GetAnymethod provides flexible device selection:
- Prioritizes specific device when
deviceIDis provided- Applies activity filter when
duration > 0- Returns random device when multiple matches exist
The use of
math/rand/v2withrand.IntNis appropriate for device selection (not cryptographic), and the//nolint:gosecis correctly justified.
121-123: LGTM: Improved cache error handling.Extracting the error to a variable before logging improves code clarity.
131-131: LGTM: Consistent error message phrasing.All error messages updated to use "failed to" phrasing.
Also applies to: 156-156, 181-181
internal/sms-gateway/handlers/upstream.go (1)
77-77: LGTM: Consistent error message phrasing.The updated log message aligns with PR-wide standardization.
internal/sms-gateway/modules/sse/service.go (3)
17-19: LGTM: Named constant improves maintainability.Extracting the buffer size to a named constant makes it easier to adjust and understand the channel capacity.
151-163: LGTM: Write failures now properly terminate the stream.The
successflag pattern correctly addresses the previous review concern—whenwriteToStreamfails,handleStreamreturns immediately, allowing the deferredremoveConnectionto clean up and keeping metrics accurate.
166-174: LGTM: Keepalive metric properly incremented after successful write.The keepalive path now correctly increments the metric only after a successful write, maintaining metric accuracy.
internal/sms-gateway/handlers/settings/mobile.go (2)
21-35: LGTM: Constructor simplification improves clarity.The direct dependency injection approach is cleaner than the fx params pattern and makes dependencies explicit.
51-57: LGTM: PII leak properly addressed.The error handling now correctly logs detailed context server-side while returning a generic error to the client, preventing device/user ID exposure in 500 responses.
internal/sms-gateway/handlers/webhooks/3rdparty.go (2)
32-40: LGTM: Constructor follows consistent pattern.The constructor properly initializes the Handler base and webhooks service.
77-81: LGTM: Pointer-based DTO with proper error handling.Using
new()for the DTO and returning afiber.NewErroron validation failure is appropriate and consistent with Fiber conventions.internal/sms-gateway/handlers/messages/mobile.go (2)
35-43: LGTM: Constructor properly initializes dependencies.The constructor correctly wires up the Handler base and messages service using the fx params pattern.
60-63: LGTM: Query parameter validation with proper error handling.Using
new()for query params and returning a Fiber 400 error on validation failure is appropriate.internal/sms-gateway/modules/messages/cache.go (3)
30-30: LGTM: Parameter naming follows Go conventions.Renaming
IDtoidis appropriate since this is an internal (unexported) method parameter.Also applies to: 53-53
46-50: LGTM: Explicit error handling improves clarity.Checking the
Setresult explicitly and wrapping the error is clearer than the previous inline approach.
63-63: LGTM: nolint comment documents intentional pattern.The comment appropriately explains why returning
(nil, nil)is intentional for caching "Not Found" state.internal/sms-gateway/handlers/settings/3rdparty.go (1)
35-44: LGTM! Consistent refactoring with improved error messages.The changes align with the broader PR refactoring:
- Constructor relocation follows the consistent pattern across handlers
- Error messages standardized to "failed to..." phrasing
- Explicit
make(map[string]any)improves clarity- The
new()pattern for validation is functionally equivalent to&Type{}All changes improve code consistency without altering functionality.
Also applies to: 56-64, 79-98, 113-131
internal/sms-gateway/handlers/messages/3rdparty.go (1)
44-53: LGTM! Enhanced error handling and observability.The changes improve the handler's robustness:
- Constructor relocation aligns with the DI pattern refactoring
- Error handling now properly distinguishes between client errors (400) and internal errors (500) using
errors.Is(err, devices.ErrNotFound)- Structured logging with
zapfields enables better debugging and monitoring- Builder pattern adoption for service calls improves code readability
- Consistent error message formatting across all endpoints
These changes enhance both the developer experience and operational observability.
Also applies to: 72-169, 188-205, 219-234, 249-272
internal/sms-gateway/modules/messages/repository.go (1)
30-110: LGTM! Clean builder pattern adoption.The refactoring to builder-style filters and options improves the API:
SelectFilterandSelectOptionswith fluent methods (e.g.,WithDeviceID(),IncludeRecipients()) provide better discoverability and type safety- Using
(*Message)(nil)for GORM model hints is a standard pattern that avoids unnecessary allocationsSelectPendingnow cleanly composes filter and options via the builder pattern- Error message standardization to "failed to..." improves consistency
All changes maintain correctness while improving code quality.
Also applies to: 112-127, 169-173, 200-200
internal/sms-gateway/modules/messages/service.go (1)
73-84: LGTM! Builder pattern adoption and past issues resolved.The refactoring delivers several improvements:
- Builder-style filters and options enhance code readability and type safety
- Past review concerns addressed:
ErrNoContentsentinel error (line 232) and cache key usingmsg.ExtID(line 244) are now correctlo.Mapandlo.MapToSliceusage reduces boilerplate while maintaining clarity- Error messages standardized to "failed to..." pattern
ExportInboxnow properly returns errors for caller handlingThese changes improve both correctness and maintainability.
Also applies to: 126-139, 141-174, 176-261, 263-271
1ea50b8 to
671b0cc
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 1
♻️ Duplicate comments (1)
internal/sms-gateway/modules/messages/service.go (1)
237-241: Address the integer overflow risk in TTL calculation.This critical issue was flagged in previous reviews and remains unresolved. The
//nolint:gosecsuppression is not justified without bounds validation.The code multiplies a user-controlled
*message.TTL(uint64) bytime.Second, then casts totime.Duration(int64). A TTL value exceeding ~9,223,372,036 seconds (~292 years) will cause silent integer overflow, potentially resulting in negative or incorrect durations.The external
smsgateway.Messagestruct has no documented TTL constraints, and the only validation is*message.TTL > 0, which does not prevent large values.Required fix: Add explicit bounds validation before the multiplication:
+const maxTTL = 315360000 // ~10 years in seconds + func (s *Service) prepareMessage(device models.Device, message MessageIn, opts EnqueueOptions) (*Message, error) { // ... existing validation ... validUntil := message.ValidUntil if message.TTL != nil && *message.TTL > 0 { + if *message.TTL > maxTTL { + return nil, ValidationError(fmt.Sprintf("TTL exceeds maximum allowed value of %d seconds", maxTTL)) + } - //nolint:gosec // not a problem validUntil = anys.AsPointer( time.Now().Add(time.Duration(*message.TTL) * time.Second), ) }Based on learnings
🧹 Nitpick comments (5)
internal/sms-gateway/modules/devices/service.go (1)
80-107: Consider aligning the return type with the existingGetmethod.The implementation logic is correct, and the random selection is appropriate for this use case. However,
GetAnyreturns*models.Device(pointer) while the existingGetmethod (line 74) returnsmodels.Device(value). This inconsistency may confuse callers and create different usage patterns across the codebase.Consider returning
models.Deviceby value for consistency:-func (s *Service) GetAny(userID string, deviceID string, duration time.Duration) (*models.Device, error) { +func (s *Service) GetAny(userID string, deviceID string, duration time.Duration) (models.Device, error) { filter := []SelectFilter{ WithUserID(userID), } if deviceID != "" { filter = append(filter, WithID(deviceID)) } if duration > 0 { filter = append(filter, ActiveWithin(duration)) } devices, err := s.devices.Select(filter...) if err != nil { - return nil, err + return models.Device{}, err } if len(devices) == 0 { - return nil, ErrNotFound + return models.Device{}, ErrNotFound } if len(devices) == 1 { - return &devices[0], nil + return devices[0], nil } idx := rand.IntN(len(devices)) //nolint:gosec //not critical - return &devices[idx], nil + return devices[idx], nil }internal/sms-gateway/modules/messages/workers.go (1)
32-32: Optional: Remove redundant mutex initialization.The explicit initialization
mux: sync.Mutex{}is unnecessary since the zero value ofsync.Mutexis already valid and ready to use. You can omit this field from the struct literal.queue: map[uint64]struct{}{}, - mux: sync.Mutex{},internal/sms-gateway/modules/messages/models.go (2)
97-110: Reconsider the nil,nil return pattern for type mismatches.Returning
(nil, nil)when the message type doesn't match violates Go's error handling convention where a nil error indicates success. This "special meaning" approach is confusing and error-prone for callers who must remember to check both return values.Consider these alternatives:
- Return a sentinel error like
ErrWrongContentTypewhen the type doesn't match- Return
(nil, nil)only for truly absent content, and an error for type mismatches- Add a
HasTextContent()boolean method so callers can check before callingExample refactor using a sentinel error:
+var ErrWrongContentType = errors.New("content type mismatch") + func (m *Message) GetTextContent() (*TextMessageContent, error) { if m.Type != MessageTypeText { - return nil, nil //nolint:nilnil // special meaning + return nil, ErrWrongContentType } // ... rest of function } func (m *Message) GetDataContent() (*DataMessageContent, error) { if m.Type != MessageTypeData { - return nil, nil //nolint:nilnil // special meaning + return nil, ErrWrongContentType } // ... rest of function }Also applies to: 124-137
147-155: Remove redundant zero-value field assignments.The explicit
ID: 0andMessageID: 0assignments are unnecessary since these are the zero values foruint64. Go automatically initializes omitted fields to their zero values.Apply this diff:
func newMessageRecipient(phoneNumber string, state ProcessingState, err *string) MessageRecipient { return MessageRecipient{ - ID: 0, - MessageID: 0, PhoneNumber: phoneNumber, State: state, Error: err, } }internal/sms-gateway/modules/messages/service.go (1)
87-90: Consider a more idiomatic builder pattern.The
*new(SelectFilter).WithExtID(...)pattern (dereferencing a pointer to a zero-value struct) is unusual in Go. While it works, it's more idiomatic to either:
- Use a constructor:
NewSelectFilter().WithExtID(...)- Use a struct literal directly if the builder adds little value
If you prefer to keep the builder pattern, add a constructor:
func NewSelectFilter() *SelectFilter { return &SelectFilter{} }Then use:
- existing, err := s.messages.Get( - *new(SelectFilter).WithExtID(message.ID).WithDeviceID(device.ID), - SelectOptions{}, //nolint:exhaustruct // not needed - ) + filter := NewSelectFilter().WithExtID(message.ID).WithDeviceID(device.ID) + existing, err := s.messages.Get(*filter, SelectOptions{})Also applies to: 155-156
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
⛔ Files ignored due to path filters (1)
go.sumis excluded by!**/*.sum
📒 Files selected for processing (8)
cmd/sms-gateway/main.go(2 hunks)go.mod(0 hunks)internal/sms-gateway/modules/devices/service.go(6 hunks)internal/sms-gateway/modules/messages/models.go(7 hunks)internal/sms-gateway/modules/messages/service.go(7 hunks)internal/sms-gateway/modules/messages/workers.go(5 hunks)internal/sms-gateway/pubsub/module.go(2 hunks)internal/version/version.go(1 hunks)
💤 Files with no reviewable changes (1)
- go.mod
✅ Files skipped from review due to trivial changes (1)
- internal/version/version.go
🚧 Files skipped from review as they are similar to previous changes (1)
- internal/sms-gateway/pubsub/module.go
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2025-07-22T00:31:28.796Z
Learnt from: capcom6
Repo: android-sms-gateway/server PR: 154
File: internal/sms-gateway/modules/sse/service.go:67-79
Timestamp: 2025-07-22T00:31:28.796Z
Learning: In the SSE service Send method (internal/sms-gateway/modules/sse/service.go), blocking on full channels is intentional and acceptable. Timeout control is handled at the calling code level through context, not within the Send method itself. The user prefers this approach over adding a default case that would drop messages when channels are full.
Applied to files:
internal/sms-gateway/modules/messages/service.go
🧬 Code graph analysis (3)
internal/sms-gateway/modules/messages/models.go (1)
internal/sms-gateway/models/models.go (1)
SoftDeletableModel(12-16)
internal/sms-gateway/modules/devices/service.go (3)
internal/sms-gateway/models/models.go (1)
Device(34-45)internal/sms-gateway/modules/devices/repository_filter.go (4)
SelectFilter(9-9)WithUserID(23-27)WithID(11-15)ActiveWithin(29-33)internal/sms-gateway/modules/devices/repository.go (1)
ErrNotFound(14-14)
internal/sms-gateway/modules/messages/service.go (4)
internal/sms-gateway/modules/messages/repository_filter.go (3)
Order(7-7)SelectFilter(16-23)SelectOptions(51-62)internal/sms-gateway/modules/messages/models.go (6)
MessageState(157-162)ProcessingState(13-13)ProcessingStatePending(17-17)MessageRecipient(139-145)Message(36-56)NewMessage(58-83)internal/sms-gateway/modules/events/events.go (1)
NewMessageEnqueuedEvent(9-11)internal/sms-gateway/modules/messages/errors.go (2)
ErrNoContent(7-7)ValidationError(10-10)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (5)
- GitHub Check: Benchmark
- GitHub Check: E2E
- GitHub Check: Lint
- GitHub Check: Test
- GitHub Check: Analyze (go)
🔇 Additional comments (13)
cmd/sms-gateway/main.go (2)
10-12: LGTM! Good refactor to eliminate magic strings.The introduction of the
cmdWorkerconstant improves code maintainability by replacing the hard-coded "worker" string throughout the file.
47-51: LGTM! Constant usage is consistent.All references to the "worker" command string have been correctly replaced with the
cmdWorkerconstant. The logic remains unchanged and correct.internal/sms-gateway/modules/devices/service.go (5)
7-7: LGTM!The
math/rand/v2import is necessary for the random device selection in the newGetAnymethod.
121-122: LGTM!The refactored error handling and updated log message improve consistency across the codebase.
131-131: LGTM!The updated log message aligns with the consistent "failed to" pattern used throughout the service.
156-156: LGTM!The updated log message maintains consistency with the "failed to" pattern.
180-189: LGTM!The refactored error handling with explicit variable names and consistent log messages improve code maintainability and readability.
internal/sms-gateway/modules/messages/workers.go (4)
52-52: LGTM!Good improvement to comment formatting for consistency.
62-63: LGTM!The migration to use the iterator-based
maps.Keyswithslices.AppendSeqand the built-inclearfunction is correct and idiomatic for Go 1.23+. The pre-allocation of the slice capacity is efficient, and the logic correctly collects all keys before clearing the queue.
73-73: LGTM!The error message change to "failed to hash messages" is more consistent with Go conventions and improves code quality.
5-9: Go version requirement satisfied.The project specifies Go 1.24.3 in
go.mod, which exceeds the minimum requirement of Go 1.23 for the standard librarymapsandslicespackages with iterator support used in this change.internal/sms-gateway/modules/messages/models.go (1)
58-83: LGTM! Well-structured constructor with proper initialization.The constructor correctly initializes Recipients with
ProcessingStatePendingstate and sets appropriate defaults. Thenolint:exhaustructdirective is justified for this partial constructor pattern.internal/sms-gateway/modules/messages/service.go (1)
206-215: Acknowledge the intentional blocking behavior.The asynchronous notification pattern here is appropriate for fire-and-forget event delivery. Error logging is handled correctly within the goroutine.
Based on learnings
217c8c1 to
e018832
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 2
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
pkg/cache/memory.go (1)
81-115: Potential data race betweenGetand TTL‑mutatingGetcallsThere’s a concurrency hole in the interaction between
Get, TTL‑mutatingGetoptions, andgetItem:
When
len(opts) == 0, the getter used byGetdoes:m.mux.RLock() item, ok := m.items[key] m.mux.RUnlock() return item, okThen
getItemis called and executesitem.isExpired(time.Now())outside the mutex.When
len(opts) > 0and options include TTL‑related flags (e.g.WithValidUntil,WithTTL,UpdateTTL,WithDefaultTTL), the options path does:m.mux.Lock() item, ok := m.items[key] if ok && !item.isExpired(time.Now()) { // mutate item.validUntil based on options item.validUntil = ... } m.mux.Unlock()This means one goroutine can be writing
item.validUntilunderm.mux.Lock()while another goroutine concurrently readsitem.validUntilinitem.isExpired(...)without holding the mutex at all. That’s a data race onmemoryItem.validUntil.Suggested fix (one of these patterns):
- Simplest: make the no‑options path also hold the mutex for the entire lifetime of
itemup to theisExpiredcheck, by moving the expiration check inside the mutex or by having the getter perform the expiration check while holding anRLockand return a copy of the value instead of the pointer; or- Ensure that once an item is inserted,
validUntilis never mutated (i.e. TTL‑relatedGetoptions return derived behavior without changing the underlyingmemoryItem), so all accesses are read‑only and safe under the current locking.As it stands, concurrent callers mixing plain
Getand TTL‑modifyingGetoptions on the same key can trigger the race detector.Also applies to: 146-156, 158-179
♻️ Duplicate comments (8)
internal/sms-gateway/modules/auth/service.go (1)
53-54: Add comment explaining the ignored error.A past review comment suggested documenting why the error from
nanoid.Standard(idLen)is safely ignored. While marked as addressed, the explanatory comment is still missing.Apply this diff:
const idLen = 21 + // nanoid.Standard with size 21 never returns an error idgen, _ := nanoid.Standard(idLen)internal/sms-gateway/modules/messages/service.go (6)
201-205: Nice fix on cache key + async notificationUsing
msg.ExtIDas the cache key when setting the initial state alignsEnqueuewithGetStateand avoids the previous issue of caching under the wrong identifier. The async notify goroutine with captureduserID/deviceIDis straightforward and keeps enqueue latency low while still logging failures.Also applies to: 206-217
243-252: prepareMessage constructor + content validation are well factoredCentralizing message construction through
NewMessageand then gating content via theswitch(text vs data vsErrNoContent) makes the enqueue path much clearer. Using wrapped errors for phone normalization and content setting should also make upstream error handling and logging easier.Also applies to: 254-265, 271-271
284-285: UsingnewMessageRecipientinrecipientsStateToModelkeeps things DRYThe refactor to build
MessageRecipientvianewMessageRecipientand convert state throughProcessingState(v.State)keeps recipient construction consistent with other paths and ensures any future defaults live in one place.Also applies to: 286-309
344-345: ValidationError usage in phone cleaning improves error semanticsReturning
ValidationErrorinstances for parse failures, invalid numbers, and non‑mobile types gives callers a clear way to distinguish validation issues from other internal errors, and avoids dynamicerrors.Newstrings. This aligns well with the dedicated validation error type introduced in this package.Also applies to: 347-349, 351-354
176-199: Avoid returning a non‑nil state when insert fails
Enqueuecurrently returns a populated*MessageStateOuttogether with a non‑nil error whenInsertfails. This breaks the usual Go convention of “on error, other return values are undefined/zero” and makes it easy for callers to accidentally usestatewithout checkingerrfirst (this pattern was already called out previously).Consider returning a nil state on insert failure instead:
- state := &MessageStateOut{ + state := &MessageStateOut{ DeviceID: device.ID, MessageStateIn: MessageStateIn{ ID: msg.ExtID, State: ProcessingStatePending, Recipients: lo.Map( msg.Recipients, func(item MessageRecipient, _ int) smsgateway.RecipientState { return modelToRecipientState(item) }, ), States: map[string]time.Time{}, }, IsHashed: false, IsEncrypted: msg.IsEncrypted, } if insErr := s.messages.Insert(msg); insErr != nil { - return state, insErr + return nil, insErr }
220-233: Add upper bound validation for TTL to avoidtime.DurationoverflowThe TTL handling still multiplies a user‑controlled
*message.TTLbytime.Secondafter casting totime.Durationand suppresses gosec with//nolint:gosec. Sincetime.Durationis anint64, sufficiently large TTLs will silently overflow and produce incorrectvalidUntilvalues. This was flagged earlier and remains unfixed.Introduce an explicit maximum TTL (in seconds), validate against it, and drop the nolint once overflow is impossible, e.g.:
- validUntil := message.ValidUntil - if message.TTL != nil && *message.TTL > 0 { - //nolint:gosec // not a problem - validUntil = anys.AsPointer( - time.Now().Add(time.Duration(*message.TTL) * time.Second), - ) - } + validUntil := message.ValidUntil + if message.TTL != nil && *message.TTL > 0 { + const maxTTLSeconds = uint64(10 * 365 * 24 * 60 * 60) // ~10 years + if *message.TTL > maxTTLSeconds { + return nil, fmt.Errorf("TTL exceeds maximum allowed value (%d seconds)", maxTTLSeconds) + } + + validUntil = anys.AsPointer( + time.Now().Add(time.Duration(*message.TTL) * time.Second), + ) + }In Go, confirm that time.Duration is a signed int64 and that converting a large uint64 TTL in seconds via time.Duration(ttl)*time.Second can overflow, and check recommended patterns for bounding user-controlled durations.Also applies to: 235-241
pkg/cache/memory_concurrency_test.go (1)
32-32: Allfor ... rangeloops overintvariables are invalid and break compilationEvery loop of the form:
for range numGoroutines {for i := range numGoroutines {for j := range numKeys / numGoroutines {for range numReaders {},for range numWriters {}, etc.for i := range numGoroutines * attemptsPerGoroutine {for range attemptsPerGoroutine {}- and similar with
numItems,numOperations / X, etc.is illegal in Go:
rangeoperates on arrays, slices, maps, strings, or channels, not on plainintvalues. As written, this file will not compile.Please rewrite all of these loops to use a conventional counter or to range over a sized slice, for example:
// Fixed patterns for i := 0; i < numGoroutines; i++ { ... } for j := 0; j < numKeys/numGoroutines; j++ { ... } for i := 0; i < numGoroutines*attemptsPerGoroutine; i++ { ... } // Or, if you really want range: for i := range make([]struct{}, numGoroutines) { ... }Make sure you apply this consistently in:
TestMemoryCache_ConcurrentReadsTestMemoryCache_ConcurrentWritesTestMemoryCache_ConcurrentReadWriteTestMemoryCache_ConcurrentSetAndGetAndDeleteTestMemoryCache_ConcurrentSetOrFailTestMemoryCache_ConcurrentDrainTestMemoryCache_ConcurrentCleanupTestMemoryCache_ConcurrentGetAndDeleteTestMemoryCache_RaceConditionDetectionThe earlier bot comment already flagged the race-test loops; this extends the same fix to all similar loops.
Also applies to: 37-39, 61-61, 66-66, 81-82, 160-160, 165-165, 360-360, 371-371, 376-376, 390-390, 399-399, 412-412, 110-110, 115-115, 128-128, 133-133, 214-214, 219-219, 256-256, 267-267, 295-295, 312-312, 327-327, 342-342
🧹 Nitpick comments (7)
internal/sms-gateway/pubsub/module.go (1)
20-22: Consider removing the no-op OnStart hook.The OnStart hook currently does nothing and returns nil. If there's no initialization needed for the pubsub component, this hook can be safely omitted—fx.Hook only requires the lifecycle methods you actually need.
Apply this diff if the no-op OnStart is not needed for consistency across modules:
fx.Invoke(func(ps PubSub, logger *zap.Logger, lc fx.Lifecycle) { lc.Append(fx.Hook{ - OnStart: func(_ context.Context) error { - return nil - }, OnStop: func(_ context.Context) error {cmd/sms-gateway/main.go (1)
10-12: Consider adding a constant for the "start" command for consistency.The introduction of
cmdWorkeris good practice, eliminating the magic string "worker". However, line 46 still uses the magic string"start". For consistency and maintainability, consider adding a constant for it as well.Apply this diff:
const ( cmdWorker = "worker" + cmdStart = "start" )Then update line 46:
- cmd := "start" + cmd := cmdStartinternal/sms-gateway/handlers/messages/3rdparty.go (1)
72-175: Consider refactoring to reduce function length.The function exceeds 100 lines (102 total), which violates the linting threshold. While the logic is clear and well-structured, extracting device selection (lines 83-100) or message preparation (lines 102-131) into helper methods could improve maintainability.
internal/sms-gateway/modules/auth/service.go (1)
236-239: Consider using the context parameter or removing it.The
cleanmethod accepts acontext.Contextparameter but doesn't use it. If future cleanup operations won't need the context, consider removing the parameter to simplify the signature.Apply this diff if the context won't be needed:
-func (s *Service) clean(_ context.Context) { +func (s *Service) clean() { s.codesCache.Cleanup() s.usersCache.Cleanup() }And update the call site:
- s.clean(ctx) + s.clean()internal/sms-gateway/modules/messages/models.go (2)
58-82: Consider reusingnewMessageRecipientinNewMessageThe constructor is clear, but the recipient initialization duplicates the defaults encoded in
newMessageRecipient. Reusing the helper would keep recipient initialization consistent if new fields/defaults are added later.- Recipients: lo.Map(phoneNumbers, func(item string, _ int) MessageRecipient { - return MessageRecipient{PhoneNumber: item, State: ProcessingStatePending} - }), + Recipients: lo.Map( + phoneNumbers, + func(item string, _ int) MessageRecipient { + return newMessageRecipient(item, ProcessingStatePending, nil) + }, + ),
85-137: Document the(nil, nil)semantics in content getters
GetTextContent/GetDataContentreturning(nil, nil)whenTypedoesn’t match is a bit non‑idiomatic and easy to misinterpret as “empty content” instead of “wrong message type”. Consider adding brief doc comments on these methods (or a package‑level comment) clarifying that(nil, nil)means “not this content type”, and reserve non‑nil errors for real failures (JSON issues).internal/sms-gateway/modules/messages/repository.go (1)
30-101: Wrap count errors for consistency with Find errors
query.Count(&total)currently returns the bare error whileFinderrors are wrapped with context. For easier debugging and log searching, consider wrapping the count error similarly:- if err := query.Count(&total).Error; err != nil { - return nil, 0, err - } + if err := query.Count(&total).Error; err != nil { + return nil, 0, fmt.Errorf("failed to count messages: %w", err) + }
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
⛔ Files ignored due to path filters (1)
go.sumis excluded by!**/*.sum
📒 Files selected for processing (17)
cmd/sms-gateway/main.go(2 hunks)go.mod(0 hunks)internal/sms-gateway/handlers/messages/3rdparty.go(8 hunks)internal/sms-gateway/modules/auth/cache.go(1 hunks)internal/sms-gateway/modules/auth/service.go(7 hunks)internal/sms-gateway/modules/devices/service.go(6 hunks)internal/sms-gateway/modules/messages/models.go(7 hunks)internal/sms-gateway/modules/messages/repository.go(6 hunks)internal/sms-gateway/modules/messages/service.go(7 hunks)internal/sms-gateway/modules/messages/workers.go(5 hunks)internal/sms-gateway/online/metrics.go(5 hunks)internal/sms-gateway/online/service.go(5 hunks)internal/sms-gateway/pubsub/module.go(2 hunks)internal/version/version.go(1 hunks)internal/worker/locker/mysql.go(4 hunks)pkg/cache/memory.go(12 hunks)pkg/cache/memory_concurrency_test.go(14 hunks)
💤 Files with no reviewable changes (1)
- go.mod
🚧 Files skipped from review as they are similar to previous changes (5)
- internal/sms-gateway/online/metrics.go
- internal/sms-gateway/modules/messages/workers.go
- internal/sms-gateway/modules/devices/service.go
- internal/sms-gateway/modules/auth/cache.go
- internal/sms-gateway/online/service.go
🧰 Additional context used
🧠 Learnings (4)
📚 Learning: 2025-09-10T23:45:45.502Z
Learnt from: capcom6
Repo: android-sms-gateway/server PR: 178
File: internal/sms-gateway/online/module.go:14-16
Timestamp: 2025-09-10T23:45:45.502Z
Learning: fx.Decorate in uber-go/fx creates module-scoped decorated dependencies, not global modifications. Using fx.Decorate(func(log *zap.Logger) *zap.Logger { return log.Named("module_name") }) is a correct pattern for creating module-specific loggers that are scoped only to that module's dependency graph.
Applied to files:
internal/sms-gateway/pubsub/module.go
📚 Learning: 2025-07-22T00:31:28.796Z
Learnt from: capcom6
Repo: android-sms-gateway/server PR: 154
File: internal/sms-gateway/modules/sse/service.go:67-79
Timestamp: 2025-07-22T00:31:28.796Z
Learning: In the SSE service Send method (internal/sms-gateway/modules/sse/service.go), blocking on full channels is intentional and acceptable. Timeout control is handled at the calling code level through context, not within the Send method itself. The user prefers this approach over adding a default case that would drop messages when channels are full.
Applied to files:
internal/sms-gateway/modules/messages/service.go
📚 Learning: 2025-09-10T11:11:43.462Z
Learnt from: capcom6
Repo: android-sms-gateway/server PR: 178
File: pkg/cache/memory.go:59-74
Timestamp: 2025-09-10T11:11:43.462Z
Learning: In pkg/cache/memory.go, the cleanup method removes expired items first, then executes the provided callback function. This means methods like Drain that use cleanup don't need additional expiration filtering since expired items are already removed before the callback runs.
Applied to files:
pkg/cache/memory.gopkg/cache/memory_concurrency_test.go
📚 Learning: 2025-09-10T23:53:42.006Z
Learnt from: capcom6
Repo: android-sms-gateway/server PR: 178
File: pkg/cache/redis.go:63-75
Timestamp: 2025-09-10T23:53:42.006Z
Learning: In pkg/cache/redis.go, the Redis cache implementation uses Redis's native field-level TTL via HExpireAt, meaning expired fields are automatically removed by Redis itself. This differs from the memory implementation where the application tracks expiration times. As a result, operations like Drain that call HGetAll will only return non-expired items since expired fields are already removed by Redis.
Applied to files:
pkg/cache/memory.go
🧬 Code graph analysis (8)
internal/worker/locker/mysql.go (1)
internal/worker/locker/locker.go (1)
ErrLockNotAcquired(9-9)
internal/sms-gateway/modules/messages/models.go (1)
internal/sms-gateway/models/models.go (1)
SoftDeletableModel(12-16)
internal/sms-gateway/handlers/messages/3rdparty.go (7)
internal/sms-gateway/handlers/devices/3rdparty.go (2)
NewThirdPartyController(24-36)ThirdPartyController(18-22)internal/sms-gateway/handlers/logs/3rdparty.go (2)
NewThirdPartyController(24-31)ThirdPartyController(20-22)internal/sms-gateway/handlers/webhooks/3rdparty.go (2)
NewThirdPartyController(32-40)ThirdPartyController(26-30)internal/sms-gateway/handlers/base/handler.go (1)
Handler(15-18)internal/sms-gateway/modules/devices/repository.go (1)
ErrNotFound(14-14)internal/sms-gateway/modules/messages/service.go (1)
EnqueueOptions(22-24)internal/sms-gateway/modules/messages/repository.go (1)
ErrMessageAlreadyExists(17-17)
internal/sms-gateway/modules/messages/service.go (5)
internal/sms-gateway/modules/messages/repository_filter.go (3)
Order(7-7)SelectFilter(16-23)SelectOptions(51-62)internal/sms-gateway/modules/messages/models.go (6)
MessageState(157-162)ProcessingState(13-13)ProcessingStatePending(17-17)MessageRecipient(139-145)Message(36-56)NewMessage(58-83)internal/sms-gateway/modules/messages/repository.go (1)
ErrMessageNotFound(16-16)internal/sms-gateway/modules/events/events.go (1)
NewMessageEnqueuedEvent(9-11)internal/sms-gateway/modules/messages/errors.go (2)
ErrNoContent(7-7)ValidationError(10-10)
internal/sms-gateway/modules/messages/repository.go (3)
internal/sms-gateway/modules/messages/repository_filter.go (3)
SelectFilter(16-23)SelectOptions(51-62)Order(7-7)internal/sms-gateway/modules/messages/models.go (3)
Message(36-56)ProcessingStatePending(17-17)MessageRecipient(139-145)internal/sms-gateway/modules/webhooks/repository_filter.go (1)
WithDeviceID(41-46)
pkg/cache/memory.go (2)
pkg/cache/options.go (3)
GetOption(49-49)AndDelete(89-93)Option(6-6)pkg/cache/cache.go (1)
Cache(5-40)
pkg/cache/memory_concurrency_test.go (1)
pkg/cache/errors.go (2)
ErrKeyNotFound(9-9)ErrKeyExists(13-13)
internal/sms-gateway/modules/auth/service.go (5)
internal/sms-gateway/online/service.go (2)
New(28-37)Service(14-17)internal/sms-gateway/modules/auth/types.go (1)
OneTimeCode(15-18)internal/sms-gateway/models/models.go (4)
User(18-24)NewUser(26-32)Device(34-45)NewDevice(47-53)pkg/crypto/passwords.go (2)
MakeBCryptHash(14-20)CompareBCryptHash(22-27)internal/sms-gateway/modules/auth/errors.go (1)
ErrAuthorizationFailed(6-6)
🪛 GitHub Check: Lint
internal/sms-gateway/handlers/messages/3rdparty.go
[failure] 72-72:
Function 'post' is too long (102 > 100) (funlen)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
- GitHub Check: Benchmark
- GitHub Check: E2E
- GitHub Check: Analyze (go)
🔇 Additional comments (33)
internal/sms-gateway/pubsub/module.go (1)
5-5: LGTM: Error wrapping improves context.The addition of error wrapping with
fmt.Errorfprovides better context when the pubsub close operation fails, and the%wverb properly preserves the error chain for inspection witherrors.Isanderrors.As.Also applies to: 26-26
cmd/sms-gateway/main.go (1)
47-51: LGTM!The refactoring correctly replaces magic strings with the
cmdWorkerconstant while preserving the original logic. The command dispatch works as expected: if the first argument is "worker", run the worker; otherwise, run the SMS gateway server.internal/worker/locker/mysql.go (2)
45-47: LGTM! Improved error variable naming.The introduction of the
lockErrvariable improves code clarity by using a more descriptive name and avoiding reuse of the genericerrvariable. The error handling logic remains correct and maintains proper error wrapping.
74-74: LGTM! Enhanced error context.The enhanced error wrapping at both locations improves debugging by providing additional context while correctly preserving error unwrapping with
%w. Line 74 includes the key for which no connection was held, and Line 85 clarifies that the lock was not held or doesn't exist.Also applies to: 85-85
internal/sms-gateway/handlers/messages/3rdparty.go (2)
43-52: LGTM! Constructor follows consistent pattern.The constructor properly initializes the controller with dependency injection and matches the pattern used in other handlers throughout the codebase.
195-211: LGTM! Error handling is comprehensive and consistent.The handlers properly validate inputs, log errors with relevant context (user_id, device_id, etc.), and return appropriate HTTP status codes. The error handling pattern is well-structured across all endpoints.
Also applies to: 226-240, 256-278
internal/sms-gateway/modules/auth/service.go (11)
69-97: LGTM! Clean refactor with proper error handling.The code generation logic is correct:
- 3 random bytes produce 0-16777215 via bit shifting
- Modulo 1000000 yields 0-999999
- Formatting as
%06densures 6-digit codesThe OneTimeCode return type and structured error handling are good improvements.
99-111: LGTM! Good separation of concerns.Delegating password hashing to the
cryptopackage and using themodels.NewUserconstructor improves modularity and consistency.
113-121: LGTM! Signature now consistent with the API.The method signature correctly accepts
*models.Userpointer, addressing the previous review feedback. The use ofmodels.NewDeviceconstructor and pointer return type maintains consistency across the service API.
127-137: LGTM! Consistent error handling.Using the sentinel
ErrAuthorizationFailederror improves consistency and enables better error checking by callers.
139-155: LGTM! Improved error wrapping and readability.Wrapping errors with
ErrAuthorizationFailedmaintains consistency, and the namedtimeoutconstant improves code clarity.
157-176: LGTM! Robust caching with improved error handling.The refactored caching approach with the custom
usersCacheproperly addresses the cache key collision concern from previous reviews. Logging cache set failures without failing authentication is the right trade-off between reliability and observability.
178-191: LGTM! Consistent with pointer-based API.The pointer return type and descriptive error messages maintain consistency across the service.
193-218: LGTM! Improved readability and error handling.Named error variables and proper cache invalidation with error logging enhance code clarity and maintainability.
64-64: codeTTL constant is properly defined.The
codeTTLconstant is correctly defined ininternal/sms-gateway/modules/auth/types.goasconst codeTTL = 5 * time.Minute, ensuring the cache initialization at line 64 is valid.
222-234: No issues found.The
Runmethod is properly integrated into the application lifecycle atinternal/sms-gateway/modules/auth/module.go:22where it's invoked withgo svc.Run(ctx). The implementation correctly handles context cancellation and periodic cleanup.
42-42: Verify thread safety of the custom usersCache wrapper.The
usersCachewrapper (internal/sms-gateway/modules/auth/cache.go) delegates directly to an external packagegithub.com/capcom6/go-helpers/cachewith no additional synchronization mechanisms. While the wrapper methods have no explicit sync primitives (Mutex, RWMutex, etc.), thread safety depends entirely on whether the underlyingcache.Cache[models.User]provides thread-safe operations by design.Action required: Verify the thread safety guarantees of the external
github.com/capcom6/go-helpers/cachepackage, or add synchronization (e.g., RWMutex) to the wrapper if the underlying cache is not thread-safe for concurrent access patterns inAuthorizeUserandChangePassword.internal/sms-gateway/modules/messages/models.go (2)
37-56: Embedding + migration wiring looks consistentEmbedding
SoftDeletableModelintoMessageand migrating usingdb.AutoMigrate(new(Message), new(MessageRecipient), new(MessageState))is coherent and keeps the schema definition centralized; the additional error wrapping on migration is also appropriate.Also applies to: 165-169
147-155: Helper forMessageRecipientinitialization is tidy
newMessageRecipientcleanly centralizes the zero‑ID, zero‑MessageID and optional error defaults for new recipients, which makes the call sites in the service easier to reason about.internal/sms-gateway/modules/messages/repository.go (4)
103-111: SelectPending delegation via filter/options looks fineBuilding a
SelectFilter/SelectOptionsand delegating toSelectkeeps the pending‑selection logic centralized; themaxPendingBatchlimit is also explicitly enforced here. The*new(T).With...pattern is a bit unusual but readable and doesn’t introduce functional issues.
112-127: Get reusing Select simplifies the query surfaceHaving
GetcallSelectand then enforce the 0/1/many invariants keeps all filter/option handling in one place, while still returning the sentinelErrMessageNotFoundandErrMultipleMessagesFoundas before. This looks correct.
143-174: Transactional UpdateState is a solid improvementWrapping the state update, state‑history upsert, and recipients update in a single transaction ensures they succeed or fail atomically, and the final error wrapping around
UpdateStategives better context to callers. The use ofMessage.ID+PhoneNumberas the recipient key is consistent with the schema.
195-201: Cleanup delete shape matches the refactored model usageUsing
Delete((*Message)(nil))keeps the model/table reference consistent withSelectand still scopes deletion correctly to non‑pending messages older thanuntil. No issues here.internal/sms-gateway/modules/messages/service.go (5)
73-84: SelectPending wiring from service to repository looks goodDefaulting an empty
ordertoMessagesOrderLIFOand then delegating toRepository.SelectPending, followed byslices.MapOrError(messages, messageToDomain), cleanly separates persistence from DTO mapping. The nolint on wrapcheck is justified given underlying errors are already wrapped.
87-90: UpdateState mapping and caching flow are coherentFetching the existing message via
SelectFilter(ExtID + DeviceID), rebuildingStatesvialo.MapToSlice, and usingrecipientsStateToModelto normalize recipient updates all look correct. Updating the DB first and then refreshing the cache withexisting.ExtIDkeeps the cache in sync with the persisted state.Also applies to: 100-111, 113-115, 117-119
126-130: SelectStates now correctly reuses the generic Select APIMutating
filter.UserIDand then callings.messages.Selectbefore mapping toMessageStateOutgives you one canonical query path, and the new"failed to select messages"wrapping makes logs clearer. The revised signature that returns both[]MessageStateOutandtotalis straightforward.Also applies to: 135-139
141-143: GetState cache and DB fallback logic looks soundThe cache‑then‑DB pattern with a nil‑sentinel for “not found” is clear, and using
user.ID+idconsistently as the key avoids the earlier mismatch problems. The updated code also ensures not‑found DB results are cached as nil to prevent repeated lookups.Also applies to: 155-156, 160-162, 168-171
277-282: ExportInbox now wraps notify errors with contextWrapping
eventsSvc.Notifyfailures with"failed to notify device"provides useful context while keeping the API simple. Returning nil on success remains straightforward.pkg/cache/memory_concurrency_test.go (2)
5-5: Use oferrors.Isanderrorsimport looks goodImporting
errorsand switching tests to useerrors.Isagainstcache.ErrKeyNotFound/cache.ErrKeyExistsmakes the tests resilient to wrapped errors and matches how the cache API now reports errors. No issues here.Also applies to: 118-118, 221-228, 284-287, 298-299, 345-346, 380-381, 393-394
246-252: Concurrent Drain and Cleanup expectations align with cache behaviorThe concurrent
DrainandCleanuptests exercise the semantics that:
Drainreturns only non-expired items and clears the cache, andCleanupremoves expired items so subsequentGetcalls seeErrKeyNotFound.This matches how
MemoryCache.cleanupfirst drops expired entries and then runs the callback/Drain logic, and validates the behavior under concurrent calls. No changes needed here.Also applies to: 272-277, 283-292, 295-301, 304-349
pkg/cache/memory.go (3)
9-23: ExportedMemoryCachetype + constructor/interface assertion look consistentRenaming the concrete type to
MemoryCache, returning*MemoryCachefromNewMemory, and assertingvar _ Cache = (*MemoryCache)(nil)keeps the public surface coherent with theCacheinterface and avoids ambiguous unexported types. The initialization of the internal map and mutex looks correct.Also applies to: 195-199
64-78:Drain+cleanupcorrectly filter expired items before draining
Draindelegating tom.cleanupand then snapshottingm.items:
- Ensures expired entries are removed before the callback runs.
- Moves the (already-cleaned) map into a local
cpyand replacesm.itemswith a fresh map while holding the lock.- Iterates over
cpyafter unlocking, which is safe because no other goroutine retains a reference to the old map.This matches the intended semantics that
Drainreturns only non-expired items and leaves the cache empty afterwards, consistent with the cache interface and prior learnings aboutcleanup.Also applies to: 181-193
123-144:Set/SetOrFailsemantics align withCacheinterface
Setalways overwrites the key with a newmemoryItembuilt throughm.newItem, andSetOrFail:
- Rejects non‑expired existing entries with
ErrKeyExists.- Treats expired entries as available and overwrites them with a fresh item.
Both operations are protected by
m.muxand match the documentedCachebehavior. No changes needed here.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 1
♻️ Duplicate comments (2)
internal/sms-gateway/handlers/messages/3rdparty.go (2)
230-231: Capitalize error message for consistency.The error message starts with lowercase "failed" while other error messages in this file use title case (e.g., line 112: "No message content provided", line 199: "Failed to retrieve message history").
Apply this diff:
- return fiber.NewError(fiber.StatusInternalServerError, "failed to get message state") + return fiber.NewError(fiber.StatusInternalServerError, "Failed to get message state")
263-269: Capitalize error messages for consistency.The error messages on lines 264 and 269 start with lowercase letters, inconsistent with the capitalized error messages used elsewhere in this file.
Apply this diff:
- return fiber.NewError(fiber.StatusInternalServerError, "failed to get device") + return fiber.NewError(fiber.StatusInternalServerError, "Failed to get device")- return fiber.NewError(fiber.StatusInternalServerError, "failed to export inbox") + return fiber.NewError(fiber.StatusInternalServerError, "Failed to export inbox")
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (2)
internal/sms-gateway/handlers/messages/3rdparty.go(8 hunks)internal/sms-gateway/modules/messages/errors.go(1 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
internal/sms-gateway/handlers/messages/3rdparty.go (6)
internal/sms-gateway/handlers/base/handler.go (1)
Handler(15-18)internal/sms-gateway/modules/messages/service.go (1)
EnqueueOptions(22-24)internal/sms-gateway/modules/messages/errors.go (2)
ValidationError(9-9)ErrNoContent(6-6)internal/sms-gateway/modules/messages/repository.go (3)
ErrMultipleMessagesFound(18-18)ErrMessageNotFound(16-16)ErrMessageAlreadyExists(17-17)internal/sms-gateway/modules/devices/repository.go (3)
ErrNotFound(14-14)ErrInvalidFilter(15-15)ErrMoreThanOne(16-16)internal/sms-gateway/modules/devices/errors.go (1)
ErrInvalidUser(6-6)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
- GitHub Check: E2E
- GitHub Check: Benchmark
- GitHub Check: Analyze (go)
🔇 Additional comments (10)
internal/sms-gateway/modules/messages/errors.go (3)
3-3: LGTM!The
errorsimport is correctly added to support the sentinel error pattern.
5-7: LGTM!The sentinel error
ErrNoContentfollows Go conventions and provides a clear, comparable error value.
9-13: LGTM! Improved naming convention.The rename from
ErrValidationtoValidationErrorbetter aligns with Go conventions where error types typically end with "Error" (e.g.,os.PathError,json.UnmarshalTypeError), while the "Err" prefix is reserved for error variables. The implementation correctly provides a simple, descriptive validation error type.internal/sms-gateway/handlers/messages/3rdparty.go (7)
44-53: LGTM! Clean constructor pattern.The explicit dependency wiring through the base.Handler pattern is clear and follows the codebase conventions.
72-82: LGTM! Consistent error handling.The parsing validation errors correctly return 400 Bad Request responses.
84-98: LGTM! Good error logging.The device selection logic includes comprehensive error logging with relevant context (user_id, device_id). The wrapped error is properly caught by the errorHandler middleware.
130-144: Error handling addressed via centralized middleware.The previous review concern about returning fiber errors is now addressed through the errorHandler middleware (line 275), which catches wrapped errors and maps them to appropriate HTTP status codes. The structured logging provides good error context.
150-155: LGTM! Improved structured logging.The route URL generation failure logging is now properly structured with relevant context fields.
189-194: LGTM! Consistent parsing pattern.The query parameter handling follows the same pattern as other handlers in this file.
250-255: LGTM! Consistent parsing pattern.The request parsing follows the established pattern in this file.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 3
♻️ Duplicate comments (3)
internal/sms-gateway/handlers/messages/3rdparty.go (3)
230-231: Standardize error message capitalization.The error message starts with a lowercase letter. This was flagged in a previous review but remains unaddressed.
- return fiber.NewError(fiber.StatusInternalServerError, "failed to get message state") + return fiber.NewError(fiber.StatusInternalServerError, "Failed to get message state")
263-264: Standardize error message capitalization.The error message starts with a lowercase letter. This was flagged in a previous review but remains unaddressed.
- return fiber.NewError(fiber.StatusInternalServerError, "failed to get device") + return fiber.NewError(fiber.StatusInternalServerError, "Failed to get device")
268-269: Standardize error message capitalization.The error message starts with a lowercase letter. This was flagged in a previous review but remains unaddressed.
- return fiber.NewError(fiber.StatusInternalServerError, "failed to export inbox") + return fiber.NewError(fiber.StatusInternalServerError, "Failed to export inbox")
🧹 Nitpick comments (7)
pkg/health/service.go (1)
40-46: Minor inconsistencies in error handling.The code has two small issues:
- Line 40 logs "failed check" (lowercase) while line 42 sets
Description: "Failed check"(capitalized). Consider standardizing the capitalization.- Line 43's
ObservedUnit: ""is redundant since empty string is the zero value for strings in Go.Apply this diff to improve consistency and remove redundancy:
- s.logger.Error("failed check", zap.String("provider", p.Name()), zap.Error(err)) + s.logger.Error("Failed check", zap.String("provider", p.Name()), zap.Error(err)) check.Checks[p.Name()] = CheckDetail{ Description: "Failed check", - ObservedUnit: "", ObservedValue: 0, Status: StatusFail, }internal/sms-gateway/modules/sse/service.go (1)
46-46: Remove redundant mutex initialization.The zero value of
sync.RWMutexis ready to use. Explicit initialization withsync.RWMutex{}is unnecessary.Apply this diff:
- mu: sync.RWMutex{}, + mu: sync.RWMutex,Or simply omit the field from initialization—Go will zero-initialize it automatically.
internal/sms-gateway/handlers/settings/3rdparty.go (2)
85-85: Consider restoring the map capacity hint.The previous capacity hint of 8 provided a minor optimization by pre-allocating space, reducing allocations during map growth. While not critical, restoring it could be beneficial if typical settings objects contain around 8 keys.
- settings := make(map[string]any) + settings := make(map[string]any, 8)
119-119: Consider restoring the map capacity hint.Similar to the
putmethod, the previous capacity hint of 8 provided a minor optimization. Consider restoring it for consistency and to reduce allocations during map growth.- settings := make(map[string]any) + settings := make(map[string]any, 8)internal/sms-gateway/modules/messages/models.go (1)
58-82: Constructor and recipient helper are consistent with GORM expectations
NewMessageplusnewMessageRecipientbuild aMessagewith pending state and a recipient per phone number, leavingID/MessageIDat zero so GORM can populate them on insert. That’s a good pattern and should work correctly withCreateand the defined associations.One behavioral note:
TypeandContentremain unset untilSetTextContent/SetDataContentare called, so callers must ensure they set content before persisting; otherwise you may rely on DB defaults or see enum issues depending on how GORM emits zero values. If that’s a concern, we could consider enforcing content-type initialization inNewMessageor adding validation before insert.Also applies to: 147-155
internal/sms-gateway/modules/messages/repository.go (2)
30-101: Select implementation is sound; consider one small robustness tweakThe query assembly (date range, ext/user/device/state filters), count-before-pagination, ordering, and optional preloads/joins all look correct and consistent with the
SelectFilter/SelectOptionstypes.One minor robustness point:
messages := make([]Message, 0, min(options.Limit, int(total)))assumesminbehaves reasonably whenoptions.Limitis zero (no limit) and whentotalis large. That’s probably fine, but ifminis custom, double‑check it doesn’t do anything surprising with zero/negative values. Worst‑case, a simplermake([]Message, 0)would avoid depending onminat all.
143-174: Transactional UpdateState greatly improves consistencyWrapping the message state update,
MessageStateinsertion, andMessageRecipientupdates in a single transaction is a solid improvement in consistency. UsingClauses(clause.OnConflict{DoNothing: true})against themessage_statesunique index and updating recipients viaModel((*MessageRecipient)(nil))+Where(message_id, phone_number)is appropriate and avoids partial updates.One assumption worth calling out: this code expects
message.IDto already be set (i.e., the message has been persisted) so thatv.MessageID = message.IDand the recipient update filter are meaningful. If there’s any chanceUpdateStatemight be called on unsaved messages, it would be safer to either guard againstmessage.ID == 0or document that as a precondition.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (10)
internal/sms-gateway/handlers/messages/3rdparty.go(8 hunks)internal/sms-gateway/handlers/settings/3rdparty.go(4 hunks)internal/sms-gateway/modules/devices/repository.go(5 hunks)internal/sms-gateway/modules/events/module.go(1 hunks)internal/sms-gateway/modules/events/service.go(7 hunks)internal/sms-gateway/modules/messages/models.go(7 hunks)internal/sms-gateway/modules/messages/repository.go(7 hunks)internal/sms-gateway/modules/sse/service.go(8 hunks)internal/version/version.go(1 hunks)pkg/health/service.go(3 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
- internal/sms-gateway/modules/devices/repository.go
- internal/version/version.go
🧰 Additional context used
🧠 Learnings (3)
📚 Learning: 2025-09-10T23:45:45.502Z
Learnt from: capcom6
Repo: android-sms-gateway/server PR: 178
File: internal/sms-gateway/online/module.go:14-16
Timestamp: 2025-09-10T23:45:45.502Z
Learning: fx.Decorate in uber-go/fx creates module-scoped decorated dependencies, not global modifications. Using fx.Decorate(func(log *zap.Logger) *zap.Logger { return log.Named("module_name") }) is a correct pattern for creating module-specific loggers that are scoped only to that module's dependency graph.
Applied to files:
internal/sms-gateway/modules/events/module.go
📚 Learning: 2025-07-22T00:31:28.796Z
Learnt from: capcom6
Repo: android-sms-gateway/server PR: 154
File: internal/sms-gateway/modules/sse/service.go:67-79
Timestamp: 2025-07-22T00:31:28.796Z
Learning: In the SSE service Send method (internal/sms-gateway/modules/sse/service.go), blocking on full channels is intentional and acceptable. Timeout control is handled at the calling code level through context, not within the Send method itself. The user prefers this approach over adding a default case that would drop messages when channels are full.
Applied to files:
internal/sms-gateway/modules/sse/service.go
📚 Learning: 2025-07-22T00:34:31.896Z
Learnt from: capcom6
Repo: android-sms-gateway/server PR: 154
File: internal/sms-gateway/modules/sse/service.go:89-92
Timestamp: 2025-07-22T00:34:31.896Z
Learning: In Go SSE implementations, when cleaning up connections with buffered channels, avoid closing the data channels directly as this can cause panics if concurrent goroutines attempt to write to them. Instead, use a separate close signal channel to notify writers that the connection should be terminated, allowing the data channels to be garbage collected naturally when the connection is removed.
Applied to files:
internal/sms-gateway/modules/sse/service.go
🧬 Code graph analysis (8)
internal/sms-gateway/modules/events/module.go (5)
internal/sms-gateway/app.go (1)
Module(33-58)internal/sms-gateway/modules/events/service.go (2)
NewService(33-52)Service(20-31)internal/sms-gateway/modules/messages/service.go (2)
NewService(40-63)Service(26-38)internal/sms-gateway/modules/settings/service.go (2)
NewService(27-35)Service(19-25)internal/sms-gateway/modules/webhooks/service.go (2)
NewService(39-50)Service(28-37)
internal/sms-gateway/handlers/settings/3rdparty.go (2)
internal/sms-gateway/handlers/messages/3rdparty.go (2)
NewThirdPartyController(44-53)ThirdPartyController(37-42)internal/sms-gateway/modules/settings/models.go (1)
DeviceSettings(10-17)
pkg/health/service.go (1)
pkg/health/types.go (3)
Provider(69-75)Checks(67-67)CheckResult(21-24)
internal/sms-gateway/handlers/messages/3rdparty.go (6)
internal/sms-gateway/handlers/base/handler.go (1)
Handler(15-18)internal/sms-gateway/modules/messages/service.go (1)
EnqueueOptions(22-24)internal/sms-gateway/modules/messages/errors.go (2)
ValidationError(9-9)ErrNoContent(6-6)internal/sms-gateway/modules/messages/repository.go (3)
ErrMultipleMessagesFound(18-18)ErrMessageNotFound(16-16)ErrMessageAlreadyExists(17-17)internal/sms-gateway/modules/devices/repository.go (3)
ErrNotFound(14-14)ErrInvalidFilter(15-15)ErrMoreThanOne(16-16)internal/sms-gateway/modules/devices/errors.go (1)
ErrInvalidUser(6-6)
internal/sms-gateway/modules/events/service.go (5)
internal/sms-gateway/pubsub/pubsub.go (1)
PubSub(15-15)internal/sms-gateway/modules/events/errors.go (1)
ErrValidationFailed(6-6)internal/sms-gateway/modules/events/metrics.go (6)
DeliveryTypeUnknown(20-20)FailureReasonPublishError(23-23)EventTypeUnknown(26-26)FailureReasonSerializationError(22-22)DeliveryTypePush(18-18)FailureReasonProviderFailed(24-24)internal/sms-gateway/modules/events/types.go (1)
Event(10-13)internal/sms-gateway/modules/sse/types.go (1)
Event(7-10)
internal/sms-gateway/modules/messages/models.go (1)
internal/sms-gateway/models/models.go (1)
SoftDeletableModel(12-16)
internal/sms-gateway/modules/messages/repository.go (2)
internal/sms-gateway/modules/messages/repository_filter.go (3)
SelectFilter(16-23)SelectOptions(51-62)Order(7-7)internal/sms-gateway/modules/messages/models.go (3)
Message(36-56)ProcessingStatePending(17-17)MessageRecipient(139-145)
internal/sms-gateway/modules/sse/service.go (2)
internal/sms-gateway/modules/sse/errors.go (1)
ErrNoConnection(6-6)internal/sms-gateway/modules/sse/metrics.go (2)
ErrorTypeMarshalError(22-22)ErrorTypeWriteFailure(21-21)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
- GitHub Check: E2E
- GitHub Check: Benchmark
- GitHub Check: Analyze (go)
🔇 Additional comments (23)
pkg/health/service.go (3)
9-21: LGTM! Clean rename fromHealthProvidertoProvider.The struct field and constructor parameter have been consistently updated to use the renamed
Providertype.
23-26: LGTM! Method signature updated consistently.The probe function parameter now correctly accepts
Providerinstead ofHealthProvider.
62-78: LGTM! Check methods updated consistently.All three check methods (
CheckReadiness,CheckLiveness,CheckStartup) have been updated to use the renamedProvidertype in their closures. The logic remains unchanged.internal/sms-gateway/modules/sse/service.go (7)
17-19: Good practice using a named constant.Defining
eventsBufferSizeas a constant improves maintainability and eliminates the magic number.
54-105: LGTM - Error handling improved.The error wrapping with
%wand descriptive messages (lines 62, 69, 98) follows Go best practices. The non-blocking send with buffer-full logging (lines 84-91) aligns with the intentional design documented in learnings.
120-131: LGTM - Clean separation of concerns.Extracting the streaming logic to
handleStreamimproves readability and testability.
133-179: LGTM - Write failure handling correctly implemented.The success flag pattern (lines 150-163) properly addresses the previous review concern: write errors are now captured and cause
handleStreamto return, triggering the deferredremoveConnectioncleanup. The conditional ticker pattern (lines 137-145) is idiomatic Go—whenkeepAlivePeriod <= 0, the nil channel effectively disables keepalives in the select.
181-192: LGTM - Proper error handling.Both write and flush errors are properly wrapped with descriptive messages and metrics are correctly incremented.
194-218: LGTM - Correct channel setup.Using the
eventsBufferSizeconstant (line 202) and maintaining the separatecloseSignalchannel aligns with best practices documented in learnings.
220-245: LGTM - Proper cleanup.The method correctly closes only the
closeSignalchannel (line 227), not the data channel, preventing potential panics as documented in learnings. Cleanup and metrics handling are correct.internal/sms-gateway/handlers/settings/3rdparty.go (2)
35-44: LGTM!The constructor follows the consistent pattern used across other handlers in this PR, properly wiring dependencies through explicit parameters.
56-64: LGTM!The error message update improves consistency across the codebase by using the "failed to" pattern.
internal/sms-gateway/handlers/messages/3rdparty.go (3)
44-53: LGTM!The constructor follows the consistent dependency injection pattern established across handlers in this PR.
72-144: LGTM!The updated error handling pattern works well with the new centralized
errorHandlermiddleware (lines 275-306), which maps domain-specific errors to appropriate HTTP status codes. The added logging provides better observability for debugging failures.
309-309: LGTM!The error handler middleware is correctly registered to intercept and process errors from all routes in this router.
internal/sms-gateway/modules/messages/models.go (4)
9-9: SoftDeletableModel embedding and new lo dependency look fineEmbedding
models.SoftDeletableModelintoMessageand addinglofor slice mapping are both reasonable and idiomatic choices here. No correctness issues spotted as long asSoftDeletableModeldoes not itself declare anIDfield (which would conflict withMessage.ID).Also applies to: 36-56
85-110: Pointer-based text content handling and error wrapping look correctThe switch to pointer semantics in
GetTextContent(usingnew(TextMessageContent)and returning(*TextMessageContent, error)) is safe and eliminates returning the address of a stack value. Thenil, nilreturn on type mismatch is clearly documented with//nolint:nilniland keeps the API simple for callers that treat “not text” as “no content”. Error wrapping inSetTextContentandGetTextContentis also fine.
112-137: Data content handling mirrors text content correctly
SetDataContent/GetDataContentmirror the text variants with pointer allocation,nil, nilsemantics on type mismatch, and consistent error wrapping. The implementation looks correct and symmetric with the text-content API.
164-169: Wrapped migration error improves debuggabilityUsing
db.AutoMigrate(new(Message), new(MessageRecipient), new(MessageState))and wrapping any error with"messages migration failed"is a straightforward improvement; no issues here.internal/sms-gateway/modules/messages/repository.go (4)
16-18: Error value changes are reasonable but may affect callersSwitching
ErrMessageNotFoundto a plainerrors.Newwhile keepingErrMessageAlreadyExistsandErrMultipleMessagesFoundas package-level sentinels is fine internally. Just ensure external callers aren’t still relying onerrors.Is(err, gorm.ErrRecordNotFound)for repo methods; they should now key offErrMessageNotFoundinstead.
103-110: SelectPending correctly reuses the generic Select APIUsing
Selectwith aSelectFilterpre-populated via fluent methods andSelectOptionsthat include recipients, a limit, and order is a clean reuse of the generic query logic. This should preserve previous semantics (pending messages for a device, ordered FIFO/LIFO) while centralizing the query code.
112-127: Get wrapper behavior is clear and consistent
Getdelegating toSelect, wrapping any underlying error, and translating 0/1/>1 results intoErrMessageNotFound/ErrMultipleMessagesFoundis straightforward. This should give callers a simple API with deterministic error contracts.
195-201: Cleanup query and Delete model usage look correctThe cleanup routine filters out pending messages and deletes only those created before
until, usingDelete((*Message)(nil)). That’s aligned with the rest of the repository’sModel((*T)(nil))pattern and should behave as intended.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 1
♻️ Duplicate comments (4)
internal/sms-gateway/handlers/messages/3rdparty.go (4)
220-231: Capitalize 500 message in get handler for consistencyThe
gethandler correctly mapsmessages.ErrMessageNotFoundto 404 and logs other failures, but the generic 500 message is still lowercase:return fiber.NewError(fiber.StatusInternalServerError, "failed to get message state")For consistency with other responses, update to:
- return fiber.NewError(fiber.StatusInternalServerError, "failed to get message state") + return fiber.NewError(fiber.StatusInternalServerError, "Failed to get message state")
250-270: Capitalize 500 messages in postInboxExport for consistency
postInboxExport’s control flow and logging look good (special-casingdevices.ErrNotFoundwhile logging unexpected errors). The remaining nits are capitalization of the 500 messages:return fiber.NewError(fiber.StatusInternalServerError, "failed to get device") return fiber.NewError(fiber.StatusInternalServerError, "failed to export inbox")To match the rest of the file:
- return fiber.NewError(fiber.StatusInternalServerError, "failed to get device") + return fiber.NewError(fiber.StatusInternalServerError, "Failed to get device") ... - return fiber.NewError(fiber.StatusInternalServerError, "failed to export inbox") + return fiber.NewError(fiber.StatusInternalServerError, "Failed to export inbox")
189-199: Capitalize 500 message in list handler for consistencyThe 500 path in
listlogs correctly but still returns a lowercase message ("failed to retrieve message history"), which is inconsistent with other user-facing errors.Consider capitalizing it:
- return fiber.NewError(fiber.StatusInternalServerError, "failed to retrieve message history") + return fiber.NewError(fiber.StatusInternalServerError, "Failed to retrieve message history")
311-312: Capitalize generic 500 error message in errorHandlerThe middleware correctly logs unexpected errors and returns a 500, but the message remains lowercase:
return fiber.NewError(fiber.StatusInternalServerError, "failed to handle request")Align with the capitalization style used elsewhere:
- return fiber.NewError(fiber.StatusInternalServerError, "failed to handle request") + return fiber.NewError(fiber.StatusInternalServerError, "Failed to handle request")
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (1)
internal/sms-gateway/handlers/messages/3rdparty.go(8 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
internal/sms-gateway/handlers/messages/3rdparty.go (6)
internal/sms-gateway/handlers/base/handler.go (1)
Handler(15-18)internal/sms-gateway/modules/messages/service.go (1)
EnqueueOptions(22-24)internal/sms-gateway/modules/messages/errors.go (2)
ValidationError(9-9)ErrNoContent(6-6)internal/sms-gateway/modules/messages/repository.go (3)
ErrMultipleMessagesFound(18-18)ErrMessageNotFound(16-16)ErrMessageAlreadyExists(17-17)internal/sms-gateway/modules/devices/repository.go (3)
ErrNotFound(14-14)ErrInvalidFilter(15-15)ErrMoreThanOne(16-16)internal/sms-gateway/modules/devices/errors.go (1)
ErrInvalidUser(6-6)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
- GitHub Check: E2E
- GitHub Check: Benchmark
- GitHub Check: Analyze (go)
🔇 Additional comments (3)
internal/sms-gateway/handlers/messages/3rdparty.go (3)
44-53: Constructor wiring for ThirdPartyController looks correctThe new
NewThirdPartyControllercorrectly propagatesLogger,Validator, and both services fromthirdPartyControllerParamsinto the embeddedbase.Handlerand struct fields. This aligns with the fx.In pattern and should integrate cleanly with DI.
72-97: post handler error flow is coherent with middleware-based handlingThe
posthandler now consistently:
- Uses
QueryParserValidator/BodyParserValidatorto return 400s viafiber.NewError.- Logs device-selection and enqueue failures with user/device context.
- Wraps downstream errors and lets the centralized
errorHandlermap domain errors to HTTP statuses.Given the presence of
errorHandler, returning wrapped errors here is reasonable and keeps the handler logic focused on request shaping and logging.Also applies to: 130-143, 150-155
315-316: Registering errorHandler middleware is appropriateUsing
router.Use(h.errorHandler)ensures all 3rd‑party routes share centralized error-to-HTTP mapping, which simplifies individual handlers and keeps status code logic in one place.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 0
♻️ Duplicate comments (3)
internal/sms-gateway/handlers/messages/3rdparty.go (3)
252-270: Align postInboxExport error responses with capitalization and middleware behaviorTwo minor points here:
Capitalization consistency (already flagged earlier):
- Line 264:
"failed to get device"- Line 269:
"failed to export inbox"Suggest:
return fiber.NewError(fiber.StatusInternalServerError, "failed to get device")
return fiber.NewError(fiber.StatusInternalServerError, "Failed to get device")```diff
return fiber.NewError(fiber.StatusInternalServerError, "failed to export inbox")
return fiber.NewError(fiber.StatusInternalServerError, "Failed to export inbox")
Optional: use errorHandler for non-
ErrNotFounddevice/export errorsFor
Get, only theErrNotFoundbranch explicitly returns a 400; all other device-level failures are turned into 500s viafiber.NewError, even thougherrorHandleris set up to mapdevices.ErrInvalidFilter,devices.ErrInvalidUser, anddevices.ErrMoreThanOneto 400.If you want consistent treatment across endpoints, you could:
- Keep the explicit 400 for invalid device ID, and
- For other errors from
Get/ExportInbox, log and return a wrapped error soerrorHandlercan classify them:
- if err != nil {
if errors.Is(err, devices.ErrNotFound) {return fiber.NewError(fiber.StatusBadRequest, "Invalid device ID")}h.Logger.Error("failed to get device", zap.Error(err), zap.String("user_id", user.ID))return fiber.NewError(fiber.StatusInternalServerError, "failed to get device")- }
- if err != nil {
if errors.Is(err, devices.ErrNotFound) {return fiber.NewError(fiber.StatusBadRequest, "Invalid device ID")}h.Logger.Error("failed to get device", zap.Error(err), zap.String("user_id", user.ID))return fmt.Errorf("failed to get device: %w", err)- }
```diff - if expErr := h.messagesSvc.ExportInbox(device, req.Since, req.Until); expErr != nil { - h.Logger.Error("failed to export inbox", zap.Error(expErr), zap.String("user_id", user.ID)) - return fiber.NewError(fiber.StatusInternalServerError, "failed to export inbox") - } + if expErr := h.messagesSvc.ExportInbox(device, req.Since, req.Until); expErr != nil { + h.Logger.Error("failed to export inbox", zap.Error(expErr), zap.String("user_id", user.ID)) + return fmt.Errorf("failed to export inbox: %w", expErr) + }That keeps client-visible 400 vs 500 behavior centralized in
errorHandler.
196-200: Capitalize 500 error messages for consistencyThe user-facing messages on internal failures are still lowercased here, while other error messages in this file are capitalized:
- Line 199:
"failed to retrieve message history"- Line 231:
"failed to get message state"To align with the rest of the handlers and prior review feedback, consider:
- return fiber.NewError(fiber.StatusInternalServerError, "failed to retrieve message history") + return fiber.NewError(fiber.StatusInternalServerError, "Failed to retrieve message history")- return fiber.NewError(fiber.StatusInternalServerError, "failed to get message state") + return fiber.NewError(fiber.StatusInternalServerError, "Failed to get message state")Also applies to: 224-231
275-313: errorHandler mapping looks correct; tweak message casing and optionally surface underlying validation textThe new
errorHandlermiddleware is a solid centralization of error-to-status mapping, and switching to a value-typedmessages.ValidationErrortarget:var msgValidationError messages.ValidationError case errors.As(err, &msgValidationError):fixes the earlier bug where
errors.Aswould never match the non-pointer type. That part looks correct.Two small follow-ups:
Capitalize the generic 500 message (already noted in prior review):
- h.Logger.Error("failed to handle request", zap.Error(err))
- return fiber.NewError(fiber.StatusInternalServerError, "failed to handle request")
- h.Logger.Error("failed to handle request", zap.Error(err))
- return fiber.NewError(fiber.StatusInternalServerError, "Failed to handle request")
2. **Optional: avoid leaking handler-level context in 400 messages** For `messages.ValidationError` / `ErrNoContent` / repository errors you currently return `fiber.NewError(..., err.Error())`. If `err` was wrapped with extra context (e.g., `"failed to enqueue message: %w"`), the client sees that whole string. If you prefer cleaner, domain-only messages on 4xx responses, you could instead use the matched value where available: ```diff - case errors.As(err, &msgValidationError): - fallthrough + case errors.As(err, &msgValidationError): + return fiber.NewError(fiber.StatusBadRequest, msgValidationError.Error()) + + case errors.Is(err, messages.ErrMultipleMessagesFound): + fallthrough case errors.Is(err, messages.ErrNoContent): - return fiber.NewError(fiber.StatusBadRequest, err.Error()) + return fiber.NewError(fiber.StatusBadRequest, err.Error())and keep
err.Error()for the non-typed cases, where the additional context is useful.
🧹 Nitpick comments (1)
internal/sms-gateway/handlers/messages/3rdparty.go (1)
84-98: Optional: rely more on errorHandler to avoid double logging/wrappingThe
GetAny/Enqueuebranches log at the handler level and then wrap the domain error withfmt.Errorf("failed to ...: %w", err). TheerrorHandlerwill later classify the wrapped error and, for “unknown” cases, log again, which can lead to duplicate logs and somewhat noisy 500 diagnostics.If you want to lean fully on the middleware for classification and most logging, you could:
- Keep the contextual log at this level for domain-classified errors only (e.g., when
errors.Is(err, devices.ErrNotFound)), and- For everything else, return the raw/just-wrapped error and let
errorHandlerdo the logging and response mapping.For example:
- if err != nil { - h.Logger.Error( - "failed to select device", - zap.Error(err), - zap.String("user_id", user.ID), - zap.String("device_id", req.DeviceID), - ) - - return fmt.Errorf("failed to select device: %w", err) - } + if err != nil { + h.Logger.Error( + "failed to select device", + zap.Error(err), + zap.String("user_id", user.ID), + zap.String("device_id", req.DeviceID), + ) + return fmt.Errorf("failed to select device: %w", err) + }…and similarly for the enqueue error, or even just
return errif the extra context in the error string is not needed on the client side (since it’s already in the log).Also applies to: 130-143
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (2)
internal/sms-gateway/handlers/messages/3rdparty.go(8 hunks)internal/sms-gateway/openapi/docs.go(6 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
internal/sms-gateway/handlers/messages/3rdparty.go (6)
internal/sms-gateway/handlers/base/handler.go (1)
Handler(15-18)internal/sms-gateway/modules/messages/service.go (1)
EnqueueOptions(22-24)internal/sms-gateway/modules/messages/errors.go (2)
ValidationError(9-9)ErrNoContent(6-6)internal/sms-gateway/modules/messages/repository.go (3)
ErrMultipleMessagesFound(18-18)ErrMessageNotFound(16-16)ErrMessageAlreadyExists(17-17)internal/sms-gateway/modules/devices/repository.go (3)
ErrNotFound(14-14)ErrInvalidFilter(15-15)ErrMoreThanOne(16-16)internal/sms-gateway/modules/devices/errors.go (1)
ErrInvalidUser(6-6)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (4)
- GitHub Check: Lint
- GitHub Check: Benchmark
- GitHub Check: E2E
- GitHub Check: Analyze (go)
🔇 Additional comments (9)
internal/sms-gateway/openapi/docs.go (6)
1-2: Generated file – verify source annotations.This file is auto-generated by swaggo/swag from Go source code annotations. The changes appear consistent with the PR's health endpoint and enum enhancements. Ensure the source annotations in handler files are correct and this file is regenerated as part of the build process.
124-148: Improved health endpoint semantics.The
/3rdparty/v1/healthendpoint has been updated to use 503 (Service Unavailable) instead of 500 for readiness failures, which is the correct HTTP status code for readiness probes. The description and summary now clearly indicate this is a readiness probe, aligning with the new dedicated health endpoints.
788-866: Excellent addition of Kubernetes-style health probes.The three new health endpoints (
/health/live,/health/ready,/health/startup) follow Kubernetes health check best practices:
- Liveness probe: Determines if the service is running and should be restarted if failing
- Readiness probe: Determines if the service can serve traffic
- Startup probe: Determines if initialization is complete
All endpoints have consistent structure with appropriate 200/503 responses. This is a valuable enhancement for cloud-native deployments.
1279-1303: LGTM – MessagePriority enum enhanced with format and descriptions.The addition of
"format": "int32"andx-enum-descriptionsimproves the OpenAPI specification for code generation and API documentation. The description forPriorityBypassThreshold(value 100) appropriately highlights its special behavior of bypassing limits and delays.
1398-1428: LGTM – ProcessingState enum enhanced with clear descriptions.The
x-enum-descriptionsarray provides helpful context for all five processing states, particularly clarifying that "Processed" means "received by device." This improves the API documentation and helps consumers understand the message lifecycle.
1686-1686: API title updated to reflect simplified branding.The SwaggerInfo.Title has been changed from "SMS Gateway for Android™ API" to "SMSGate API", simplifying the API name. This appears to be part of the broader rebranding effort in this migration PR.
internal/sms-gateway/handlers/messages/3rdparty.go (3)
44-53: Constructor wiring matches fx.In pattern and base.Handler usage
NewThirdPartyControllercleanly wiresmessagesSvc,devicesSvc, and the embeddedbase.Handler(logger/validator), consistent with other 3rdparty controllers and Fx’sfx.Inpattern. No issues from a construction or dependency-injection standpoint.
72-82: Validation errors correctly normalized to HTTP 400Routing both query and body parsing/validation failures through
fiber.NewError(fiber.StatusBadRequest, err.Error())standardizes bad input handling and lets the error middleware pass Fiber’s structured 400 back to the client. This is a good fit with the newerrorHandlermiddleware.
315-323: Register wiring with errorHandler middleware looks coherentRegistering
router.Use(h.errorHandler)before the route handlers ensures all errors propagate through the centralized mapping without changing the existing userauth wrappers or route signatures. The naming (route3rdPartyGetMessage) and usage inGetRouteURLremain consistent.
🤖 Pull request artifacts
|
3e4ac02 to
bd00981
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 3
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (3)
internal/sms-gateway/modules/auth/service.go (1)
127-137: Fixfmt.Errorfwith multiple%wverbs at line 142The
AuthorizeDevicefunction wraps errors using two%wverbs:fmt.Errorf("%w: %w", ErrAuthorizationFailed, err). In Go's error formatting, only the last%wchains witherrors.Is/errors.As, so callers can detect onlyerr, not theErrAuthorizationFailedsentinel. This defeats the purpose of the sentinel error.Change to use a single
%wand format the other value as plain text:- return device, fmt.Errorf("%w: %w", ErrAuthorizationFailed, err) + return device, fmt.Errorf("%w: %v", ErrAuthorizationFailed, err)or wrap the actual error:
return device, fmt.Errorf("authorize device: %w", err)The rest of
AuthorizeDevice(including the timeout-boundSetOnlinecall) looks good.internal/sms-gateway/modules/devices/repository.go (1)
45-54: Inconsistent error handling in Exists method.The repository defines
ErrNotFound = errors.New("record not found")at line 14, but theExistsmethod still checks forgorm.ErrRecordNotFoundat line 47. This creates inconsistency:
- The custom
ErrNotFounddefined in this package is never returned byExists- Other methods like
Get(line 63) return the customErrNotFound- Callers checking for
devices.ErrNotFoundwon't catch the not-found case inExistsEither:
- Update
Existsto checkerrors.Is(err, ErrNotFound)and ensure GORM errors are wrapped, or- Document that
Existsintentionally uses different error semantics (returns false with nil error for not found)The current behavior (returning
false, nilwhen not found) appears intentional based on the doc comment, but the inconsistency with the package-levelErrNotFounddefinition is confusing.</invoke_comment_end -->
pkg/cache/memory.go (1)
81-115: Potential data race betweenGetfast path and TTL‑updatingGetcallsThe fast path (
len(opts) == 0) grabsitemunderRLockand then releases the lock beforegetItemcallsitem.isExpired(time.Now()). In the options path, when TTL options are used, the same*memoryItem’svalidUntilfield is mutated under a write lock. That means one goroutine can readitem.validUntilwhile another writes it, which is a data race.To avoid the race without broad refactors, update the TTL path to create a new
memoryItemand replace the map entry instead of mutating the existing one in place, so previously returned pointers are effectively immutable:func (m *MemoryCache) Get(_ context.Context, key string, opts ...GetOption) ([]byte, error) { return m.getValue(func() (*memoryItem, bool) { @@ - m.mux.Lock() - item, ok := m.items[key] - - if ok && o.delete { - delete(m.items, key) - } else if ok && !item.isExpired(time.Now()) { - switch { - case o.validUntil != nil: - item.validUntil = *o.validUntil - case o.setTTL != nil: - item.validUntil = time.Now().Add(*o.setTTL) - case o.updateTTL != nil: - item.validUntil = item.validUntil.Add(*o.updateTTL) - case o.defaultTTL: - item.validUntil = time.Now().Add(m.ttl) - } - } - m.mux.Unlock() - - return item, ok + now := time.Now() + + m.mux.Lock() + item, ok := m.items[key] + + if ok && o.delete { + delete(m.items, key) + m.mux.Unlock() + return nil, false + } + + if ok && !item.isExpired(now) { + newValidUntil := item.validUntil + + switch { + case o.validUntil != nil: + newValidUntil = *o.validUntil + case o.setTTL != nil: + newValidUntil = now.Add(*o.setTTL) + case o.updateTTL != nil: + newValidUntil = newValidUntil.Add(*o.updateTTL) + case o.defaultTTL: + newValidUntil = now.Add(m.ttl) + } + + // Create a new item to keep previously returned pointers immutable. + item = &memoryItem{ + value: item.value, + validUntil: newValidUntil, + } + m.items[key] = item + } + + m.mux.Unlock() + return item, ok }) }This keeps external behavior the same while avoiding concurrent reads/writes of
memoryItem.validUntil.
♻️ Duplicate comments (14)
.golangci.yml (1)
347-350: Remove dead configuration for disabled linter (revisited from previous review).The
inamedparamlinter is disabled (line 127 is commented out), but its settings remain configured here with no effect. This orphaned block should be removed to keep the configuration clean and avoid future confusion.Apply this diff to remove the dead configuration:
- inamedparam: - # Skips check for interface methods with only a single parameter. - # Default: false - skip-single-param: true -Alternatively, if
inamedparamis intended to be enabled, uncomment line 127 and keep (or adjust) this configuration as needed.internal/sms-gateway/modules/auth/service.go (1)
37-50: Handle or document ignored nanoid error and new cachesThe addition of
usersCacheand initializing both caches inNewlooks consistent and keeps cleanup centralized viaRun/clean.However,
nanoid.Standard(idLen)still has its error silently discarded:const idLen = 21 idgen, _ := nanoid.Standard(idLen)Even if
21is within the safe range today, either:
- Check and handle/log the error; or
- Add a brief comment explaining why this can’t fail with a compile-time constant
idLenin the supported range, so ignoring it is intentional.This keeps future maintainers from wondering if an error was forgotten.
Also applies to: 52-67
internal/version/version.go (1)
7-7: Grammar error persists from previous review.The comment still contains the grammatical error "when build" which should be "during build" or "when building". This was already flagged in a previous review but remains unfixed.
Apply this diff to fix the grammar:
-// This information will be collected when build, by `-ldflags "-X main.appVersion=0.1"`. +// This information will be collected during build, by `-ldflags "-X main.appVersion=0.1"`..goreleaser.yaml (1)
38-60: Docker image tagging mismatch remains unresolved.The
dockers_v2configuration produces multi-arch manifest images with tags likelatest,v{{ .Major }}, etc., but does not generate per-architecture tags such aslatest-amd64orlatest-arm64. If the pr.yml workflow (referenced in past reviews) still attempts to use architecture-specific tags, those operations will fail.Ensure that:
- The GoReleaser config matches what downstream workflows expect, OR
- Workflows are updated to use only the tags GoReleaser actually produces
Based on the past review comment, this issue was already identified but appears unresolved in the current draft PR.
</invoke_comment_end -->
internal/sms-gateway/handlers/mobile.go (1)
274-276: Handle or explicitly justify ignoring the nanoid.Standard error.The ID generator is still initialized as:
const idGenSize = 21 idGen, _ := nanoid.Standard(idGenSize)Silently discarding the error can hide unexpected initialization failures. Either handle the error (and fail fast) or add a short comment explaining why it is safe to ignore for this constant size.
For example, to handle it:
- const idGenSize = 21 - idGen, _ := nanoid.Standard(idGenSize) + const idGenSize = 21 + idGen, err := nanoid.Standard(idGenSize) + if err != nil { + params.Logger.Fatal("failed to initialize ID generator", zap.Error(err)) + }Or, if you’re certain it cannot fail for 21:
const idGenSize = 21 - idGen, _ := nanoid.Standard(idGenSize) + // nanoid.Standard with size 21 is guaranteed not to return an error. + idGen, _ := nanoid.Standard(idGenSize)Confirm from the `github.com/jaevor/go-nanoid` documentation whether `Standard(21)` can ever return a non-nil error, and if not, note the guarantee so it can be documented next to this call..github/workflows/pr.yml (2)
91-103: Pin the custom S3 upload action to a stable tag or SHA.
capcom6/upload-s3-action@masteris still pointing at a moving branch, which is a supply‑chain risk and can introduce non‑reproducible builds. Prefer pinning to a specific release tag or commit SHA.Example (replace with the actual tag or SHA you intend to support):
- - name: Upload to S3 - uses: capcom6/upload-s3-action@master + - name: Upload to S3 + uses: capcom6/upload-s3-action@vX.Y.Z # or a specific commit SHAFind the latest stable release tag (or recommended pinning approach) for the GitHub Action `capcom6/upload-s3-action` so it can be referenced immutably instead of `@master`.
105-118: Verify that GoReleaser produces thelatest-arm64/latest-amd64tags used here.The
Push imagesstep assumes images exist with:latest-arm64and:latest-amd64tags:docker tag $REG/$PROJECT:latest-arm64 ... docker tag $REG/$PROJECT:latest-amd64 ...If your GoReleaser docker config only emits
:latest(multi‑arch) and versioned tags, these per‑arch tags won’t exist and the step will fail. Either:
- Update
.goreleaser.yamlto build/publish per‑arch images with those tags, or- Adjust this step to use the actual tags GoReleaser produces (or rely on GoReleaser’s own docker publishing).
#!/bin/bash # Verify whether GoReleaser config defines per-arch docker tags matching latest-{amd64,arm64} fd -e yml -e yaml | grep -i goreleaser || true if [ -f .goreleaser.yaml ]; then echo "=== Checking .goreleaser.yaml for docker image tags ===" rg -n "dockers" .goreleaser.yaml -n -C3 || true rg -n "latest-arm64|latest-amd64" .goreleaser.yaml || true fiinternal/sms-gateway/modules/events/module.go (1)
16-17:fx.Privateplacement likely leavesnewMetricsexported (same concern as earlier review).As noted in a previous review, in
fx.Provide(newMetrics, fx.Private),
fx.Privateonly affects constructors after it in the samefx.Providecall, so it won’t actually makenewMetricsprivate. If you wantnewMetricsto be module-private, the usual pattern is:fx.Provide( fx.Private, newMetrics, )If exporting metrics broadly is intentional, then no change is needed.
Confirm from the uber-go/fx documentation: does placing `fx.Private` *before* constructors in `fx.Provide` (e.g., `fx.Provide(fx.Private, newMetrics)`) mark those constructors private, while placing it after (`fx.Provide(newMetrics, fx.Private)`) has no effect on the preceding constructors?internal/config/module.go (1)
90-91: Fix API path defaulting to respect user configuration.The current logic overwrites
cfg.HTTP.API.Pathwhenevercfg.HTTP.API.Hostis empty, even if the user explicitly configured a custom path. This will surprise users who set a path but rely on the default (empty) host.Apply this diff to default the path only when it's actually empty:
- if cfg.HTTP.API.Host == "" { + if cfg.HTTP.API.Path == "" { cfg.HTTP.API.Path = "/api" }This ensures user-supplied paths are preserved regardless of the host configuration.
internal/sms-gateway/modules/push/upstream/client.go (2)
29-35: Redundant zero-value initialization.Lines 32-33 explicitly initialize
client: nilandmux: sync.Mutex{}, but these are the zero values for their respective types and don't require explicit initialization.Apply this diff:
func New(options map[string]string) (*Client, error) { return &Client{ options: options, - client: nil, - mux: sync.Mutex{}, }, nil }
62-62: Remove unnecessary type conversion.
UpstreamPushRequestis a type alias for[]smsgateway.PushNotification. Sincepayloadis already the correct slice type, the explicit conversion is redundant and thenolint:unconvertdirective suppresses a valid linter warning.Apply this diff:
- payloadBytes, err := json.Marshal(smsgateway.UpstreamPushRequest(payload)) //nolint:unconvert //type checking + payloadBytes, err := json.Marshal(payload)internal/sms-gateway/modules/messages/service.go (2)
197-199: Clarify error handling contract when insert fails.Returning both a non-nil
stateand an error on insert failure (lines 197-199) violates the standard Go pattern where functions return(nil, error)on failure. While the current caller athandlers/messages/3rdparty.go:130safely checks the error first, this pattern is error-prone and relies on caller discipline.A past review flagged this issue and was marked as "Addressed in commit 7db6313", but the code still returns
(state, insErr).Apply this diff to follow Go conventions:
if insErr := s.messages.Insert(msg); insErr != nil { - return state, insErr + return nil, insErr }
236-241: Address integer overflow risk in TTL calculation.The
nolint:gosecsuppression on line 237 is unjustified. The code multiplies user-controlled*message.TTL(uint64) bytime.Second, then casts totime.Duration(int64). Without bounds checking, a TTL value exceeding ~9,223,372,036 seconds (~292 years) will cause silent integer overflow.A past review flagged this issue and was marked as "Addressed in commit 7db6313", but the code still lacks bounds validation.
Apply this diff to add explicit bounds validation:
validUntil := message.ValidUntil if message.TTL != nil && *message.TTL > 0 { - //nolint:gosec // not a problem + const maxTTL = 315360000 // ~10 years in seconds + if *message.TTL > maxTTL { + return nil, fmt.Errorf("TTL exceeds maximum allowed value of %d seconds", maxTTL) + } validUntil = anys.AsPointer( time.Now().Add(time.Duration(*message.TTL) * time.Second), ) }internal/sms-gateway/handlers/messages/params.go (1)
24-30: Fix date-range validation to compare parsed timestamps.Line 25 compares
p.StartDate > p.EndDatelexicographically, which can incorrectly reject valid date ranges when timezone offsets differ. For example,"2025-11-10T12:00:00+02:00"is lexicographically greater than"2025-11-10T11:00:00+00:00"even though they represent the same moment in time.A past review flagged this issue and was marked as addressed, but the code still uses string comparison.
Apply this diff to parse and compare chronologically:
func (p *thirdPartyGetQueryParams) Validate() error { - if p.StartDate != "" && p.EndDate != "" && p.StartDate > p.EndDate { - return errors.New("`from` date must be before `to` date") //nolint:err113 // won't be used directly + if p.StartDate != "" && p.EndDate != "" { + from, err := time.Parse(time.RFC3339, p.StartDate) + if err != nil { + return errors.New("`from` date must be before `to` date") //nolint:err113 // won't be used directly + } + + to, err := time.Parse(time.RFC3339, p.EndDate) + if err != nil { + return errors.New("`from` date must be before `to` date") //nolint:err113 // won't be used directly + } + + if from.After(to) { + return errors.New("`from` date must be before `to` date") //nolint:err113 // won't be used directly + } } return nil }
🧹 Nitpick comments (17)
internal/sms-gateway/handlers/base/handler_test.go (1)
19-19: Formatting change: struct tag alignment.The extra spaces in struct tags appear to be for visual alignment. While this is cosmetic and has no functional impact, ensure consistent formatting is applied across the codebase.
Also applies to: 24-24, 29-29, 33-33
Makefile (1)
26-28: Coverage generation now re-runs tests.The
coveragetarget depends ontest, so invokingmake coveragewill re-run all tests. While this ensures fresh coverage data, consider whether this is the intended behavior. If tests have already been run, you might prefer a target that generates coverage from an existingcoverage.outwithout re-running tests.If independent coverage generation is desired, consider adding an optional target:
coverage-report: ## Generate coverage report from existing data go tool cover -func=coverage.out go tool cover -html=coverage.out -o coverage.htmlThis allows users to regenerate reports without re-running tests, while keeping the
coveragetarget for full coverage pipeline execution.internal/sms-gateway/handlers/upstream.go (1)
97-106: Consider making rate limit values configurable.The rate limit constants are an improvement over inline magic numbers. However, rate limits are typically operational parameters that benefit from being configurable per environment without code changes.
Consider adding fields to the
Configstruct (e.g.,UpstreamRateLimitandUpstreamRateDuration) to allow runtime configuration while keeping these constants as defaults.internal/sms-gateway/modules/db/health.go (1)
20-21: Optional: Redundant field initialization.The explicit initialization
failedPings: atomic.Int64{}is redundant since Go zero-initializes all struct fields automatically. The atomic.Int64 will have the same zero value whether explicitly initialized or not.You can simplify by removing this line, or keep it for explicitness. Both approaches are valid.
Apply this diff to remove the redundant initialization:
func newHealth(db *sql.DB) *health { return &health{ db: db, - - failedPings: atomic.Int64{}, } }internal/sms-gateway/handlers/middlewares/userauth/userauth.go (1)
115-124: Align unauthorized handling with other middlewares (optional)
WithUsercurrently returnsfiber.NewError(fiber.StatusUnauthorized, "Unauthorized")whileNewBasic,NewCode, andUserRequiredusefiber.ErrUnauthorized. For consistency (and to reuse Fiber’s shared error value), consider switching this branch toreturn fiber.ErrUnauthorizedinstead.internal/sms-gateway/pubsub/pubsub.go (2)
4-18: SentinelErrInvalidSchemedefinition looks good; consider clarifying/aligning error contractsImporting
"errors"and introducingErrInvalidSchemeas an exported sentinel is a solid move for callers to distinguish config errors viaerrors.Is. You might want to (a) document in this package that invalid schemes are treated as configuration errors, and/or (b) consider whether this should align with the existingpubsub.ErrInvalidConfigcontract so higher layers don’t have to special‑case multiple config‑type sentinels.Please double‑check other callers constructing pubsub configs to ensure they handle
ErrInvalidScheme(and/orpubsub.ErrInvalidConfig) consistently in their error mapping logic.
24-50: Centralized error handling inNewis correct; small readability tweaks are possibleThe refactor to parse once, create
pubSubin the switch, and then wrap any backend error with"failed to create pubsub"is clean and correct. A couple of small readability improvements you might consider:
- Build the options slice directly instead of creating then appending.
- In the
"memory"branch, avoid the slightly unusualpubSub, err = pubsub.NewMemory(opts...), nilmulti‑assignment and just assignpubSub(leavingerruntouched, which is alreadynilafter the parse check).Example diff for both tweaks:
- opts := []pubsub.Option{} - opts = append(opts, pubsub.WithBufferSize(config.BufferSize)) + opts := []pubsub.Option{ + pubsub.WithBufferSize(config.BufferSize), + } @@ var pubSub PubSub switch u.Scheme { case "memory": - pubSub, err = pubsub.NewMemory(opts...), nil + pubSub = pubsub.NewMemory(opts...) case "redis":Behavior remains the same but is a bit easier to read at a glance.
If you adopt these tweaks, please rerun the existing tests around pubsub initialization (both memory and redis) to confirm there are no regressions.
internal/sms-gateway/modules/auth/repository.go (1)
18-29: Pointer returns are fine; consider clarifying namingThe switch to
*models.User(withnew(models.User)andTake(user)) andModel((*models.User)(nil))is idiomatic for GORM and matches the service’s pointer-based API.One small nit:
GetByLoginstill queriesWHERE id = ?, so “login” effectively is the primary key. If you ever introduce a separate login/username field, this method name vs. query mismatch will become confusing; consider either renaming the method/comment or explicitly documenting thatloginis the user ID.Also applies to: 35-37
internal/sms-gateway/modules/auth/cache.go (1)
28-35: Align cache error semantics with caller expectations
usersCache.Getwraps all underlying errors as"failed to get user from cache: %w", butService.AuthorizeUsertreats any non-nil error as a simple cache miss and never logs it. That means the extra wrapping work isn’t observable and also hides the ability to distinguish “not found” vs real cache failures if you later decide to log or metric them.Consider one of:
- Returning the underlying error as-is and letting callers decide how to handle/log it; or
- Mapping a “not found” sentinel from
cache.Cacheto(models.User{}, nil)while only wrapping/logging unexpected failures.This would make cache behavior clearer and slightly simplify error handling.
Also applies to: 37-51
internal/sms-gateway/modules/push/service.go (1)
16-21: Debounce clamping makes the Enqueue doc comment stale.
Newnow enforcesconfig.Debounce = max(config.Debounce, defaultDebounce)withdefaultDebounce = 5 * time.Second, so a zero debounce is no longer possible. The Enqueue comment still says it “immediately sends all messages if the debounce is 0”, which is now misleading.Either update the comment to reflect the clamped behavior, or reintroduce a special “0 means immediate send” path if that semantic is still desired.
Also applies to: 60-62, 75-89, 91-91
internal/sms-gateway/modules/push/fcm/utils.go (1)
7-19: Aligning withclient.Eventis correct; consider renaming localjsonvariableSwitching to
client.Eventmatches the new push client abstraction and keeps behavior intact. The only small nit is thejsonvariable name shadowing the imported package; renaming it (e.g., todataJSON) would slightly improve readability:-func eventToMap(event client.Event) (map[string]string, error) { - json, err := json.Marshal(event.Data) +func eventToMap(event client.Event) (map[string]string, error) { + dataJSON, err := json.Marshal(event.Data) if err != nil { return nil, fmt.Errorf("failed to marshal event data: %w", err) } return map[string]string{ "event": string(event.Type), - "data": string(json), + "data": string(dataJSON), }, nil }internal/sms-gateway/modules/webhooks/errors.go (1)
18-20: Validation error formatting and detection are solidFormatting
ValidationErrorasinvalid "field" = "value": innerErris log‑friendly, and usingerrors.As(err, new(ValidationError))allows callers to detect validation errors through wrapped chains without relying on string matching.If you ever need access to the concrete
ValidationError, consider switchingIsValidationErrorto accept a*ValidationErrorout‑param, e.g.,func AsValidationError(err error, out *ValidationError) bool.Also applies to: 34-36
internal/sms-gateway/modules/push/client/types.go (1)
1-13: Push client abstraction looks solid; consider clarifyingSenderror slice semantics.The
Clientinterface andMessage/Eventtypes are well-shaped for FCM/upstream backends and the context-aware lifecycle is clear. The only thing I’d consider adding (in comments or docs) is an explicit contract forSend— e.g., whether the[]erroris always the same length asmessages, whether it can benilon total failure, and how callers should interpret per-message vs global errors. That would make downstream usage less ambiguous.internal/sms-gateway/modules/settings/models.go (1)
11-24: DeviceSettings refactor and constructor look good.Embedding
models.TimedModeldirectly intoDeviceSettingsis consistent with the shared model patterns and gives you created/updated timestamps on this table. TheNewDeviceSettingsconstructor nicely captures the required fields while intentionally leavingTimedModel/Userunset for GORM/DB to manage. The switch todb.AutoMigrate(new(DeviceSettings))is also fine and matches the style used elsewhere.Also applies to: 27-31
internal/sms-gateway/modules/sse/errors.go (1)
1-7: Good use of a static sentinel error for SSE “no connection” cases.Defining
ErrNoConnectionas a package-level sentinel is the right move for letting callers classifyerrors.Is(err, ErrNoConnection). As a small polish, you might add a brief doc comment and/or make the message slightly more contextual (e.g."sse: no connection") so logs are self-explanatory, but the current form is functionally fine.internal/sms-gateway/modules/sse/service.go (1)
46-46: Optional: Redundant mutex initialization.Explicitly initializing
mu: sync.RWMutex{}is redundant since the zero value ofsync.RWMutexis already ready to use. You can safely omit this field from the struct literal.Apply this diff if you prefer to remove the redundancy:
return &Service{ config: config, - - mu: sync.RWMutex{}, connections: make(map[string][]*sseConnection), logger: logger,internal/sms-gateway/modules/push/fcm/client.go (1)
21-27: Redundant zero-value initialization.Lines 24-25 explicitly initialize
client: nilandmux: sync.Mutex{}, but these are the zero values for their respective types and don't require explicit initialization.Apply this diff:
func New(options map[string]string) (*Client, error) { return &Client{ options: options, - client: nil, - mux: sync.Mutex{}, }, nil }
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
⛔ Files ignored due to path filters (1)
go.sumis excluded by!**/*.sum
📒 Files selected for processing (107)
.github/workflows/docker-build.yml(0 hunks).github/workflows/docker-publish.yml(0 hunks).github/workflows/go.yml(3 hunks).github/workflows/pr.yml(1 hunks).github/workflows/release.yml(2 hunks).gitignore(1 hunks).golangci.yml(1 hunks).goreleaser.yaml(1 hunks)Dockerfile.goreleaser(1 hunks)Makefile(1 hunks)cmd/sms-gateway/main.go(2 hunks)go.mod(0 hunks)internal/config/config.go(3 hunks)internal/config/module.go(1 hunks)internal/sms-gateway/app.go(2 hunks)internal/sms-gateway/cache/errors.go(1 hunks)internal/sms-gateway/cache/factory.go(2 hunks)internal/sms-gateway/handlers/base/handler.go(1 hunks)internal/sms-gateway/handlers/base/handler_test.go(5 hunks)internal/sms-gateway/handlers/converters/messages.go(1 hunks)internal/sms-gateway/handlers/devices/3rdparty.go(4 hunks)internal/sms-gateway/handlers/events/mobile.go(1 hunks)internal/sms-gateway/handlers/health.go(2 hunks)internal/sms-gateway/handlers/logs/3rdparty.go(2 hunks)internal/sms-gateway/handlers/messages/3rdparty.go(8 hunks)internal/sms-gateway/handlers/messages/mobile.go(3 hunks)internal/sms-gateway/handlers/messages/params.go(3 hunks)internal/sms-gateway/handlers/middlewares/userauth/userauth.go(2 hunks)internal/sms-gateway/handlers/mobile.go(8 hunks)internal/sms-gateway/handlers/module.go(1 hunks)internal/sms-gateway/handlers/root.go(1 hunks)internal/sms-gateway/handlers/settings/3rdparty.go(4 hunks)internal/sms-gateway/handlers/settings/mobile.go(2 hunks)internal/sms-gateway/handlers/upstream.go(4 hunks)internal/sms-gateway/handlers/webhooks/3rdparty.go(4 hunks)internal/sms-gateway/handlers/webhooks/mobile.go(2 hunks)internal/sms-gateway/models/migration.go(2 hunks)internal/sms-gateway/models/models.go(2 hunks)internal/sms-gateway/models/module.go(1 hunks)internal/sms-gateway/modules/auth/cache.go(1 hunks)internal/sms-gateway/modules/auth/errors.go(1 hunks)internal/sms-gateway/modules/auth/module.go(1 hunks)internal/sms-gateway/modules/auth/repository.go(1 hunks)internal/sms-gateway/modules/auth/service.go(7 hunks)internal/sms-gateway/modules/auth/types.go(1 hunks)internal/sms-gateway/modules/db/health.go(3 hunks)internal/sms-gateway/modules/db/module.go(2 hunks)internal/sms-gateway/modules/devices/repository.go(5 hunks)internal/sms-gateway/modules/devices/repository_filter.go(1 hunks)internal/sms-gateway/modules/devices/service.go(6 hunks)internal/sms-gateway/modules/events/errors.go(1 hunks)internal/sms-gateway/modules/events/metrics.go(3 hunks)internal/sms-gateway/modules/events/module.go(1 hunks)internal/sms-gateway/modules/events/service.go(7 hunks)internal/sms-gateway/modules/events/types.go(2 hunks)internal/sms-gateway/modules/messages/cache.go(2 hunks)internal/sms-gateway/modules/messages/converters.go(1 hunks)internal/sms-gateway/modules/messages/errors.go(1 hunks)internal/sms-gateway/modules/messages/models.go(7 hunks)internal/sms-gateway/modules/messages/module.go(1 hunks)internal/sms-gateway/modules/messages/repository.go(7 hunks)internal/sms-gateway/modules/messages/repository_filter.go(2 hunks)internal/sms-gateway/modules/messages/service.go(7 hunks)internal/sms-gateway/modules/messages/service_test.go(0 hunks)internal/sms-gateway/modules/messages/workers.go(5 hunks)internal/sms-gateway/modules/metrics/handler.go(1 hunks)internal/sms-gateway/modules/metrics/module.go(1 hunks)internal/sms-gateway/modules/push/client.go(1 hunks)internal/sms-gateway/modules/push/client/types.go(1 hunks)internal/sms-gateway/modules/push/fcm/client.go(4 hunks)internal/sms-gateway/modules/push/fcm/errors.go(1 hunks)internal/sms-gateway/modules/push/fcm/utils.go(1 hunks)internal/sms-gateway/modules/push/module.go(2 hunks)internal/sms-gateway/modules/push/service.go(10 hunks)internal/sms-gateway/modules/push/types.go(3 hunks)internal/sms-gateway/modules/push/upstream/client.go(4 hunks)internal/sms-gateway/modules/settings/models.go(1 hunks)internal/sms-gateway/modules/settings/module.go(1 hunks)internal/sms-gateway/modules/settings/repository.go(5 hunks)internal/sms-gateway/modules/settings/service.go(3 hunks)internal/sms-gateway/modules/settings/utils.go(4 hunks)internal/sms-gateway/modules/sse/config.go(2 hunks)internal/sms-gateway/modules/sse/errors.go(1 hunks)internal/sms-gateway/modules/sse/metrics.go(3 hunks)internal/sms-gateway/modules/sse/module.go(1 hunks)internal/sms-gateway/modules/sse/service.go(8 hunks)internal/sms-gateway/modules/webhooks/errors.go(3 hunks)internal/sms-gateway/modules/webhooks/models.go(1 hunks)internal/sms-gateway/modules/webhooks/module.go(1 hunks)internal/sms-gateway/modules/webhooks/repository.go(2 hunks)internal/sms-gateway/modules/webhooks/repository_filter.go(1 hunks)internal/sms-gateway/modules/webhooks/service.go(5 hunks)internal/sms-gateway/online/metrics.go(5 hunks)internal/sms-gateway/online/service.go(5 hunks)internal/sms-gateway/openapi/docs.go(6 hunks)internal/sms-gateway/pubsub/module.go(2 hunks)internal/sms-gateway/pubsub/pubsub.go(2 hunks)internal/version/version.go(1 hunks)internal/worker/app.go(1 hunks)internal/worker/config/config.go(1 hunks)internal/worker/config/types.go(2 hunks)internal/worker/executor/metrics.go(1 hunks)internal/worker/executor/service.go(3 hunks)internal/worker/locker/mysql.go(4 hunks)pkg/cache/errors.go(1 hunks)pkg/cache/memory.go(12 hunks)pkg/cache/memory_bench_test.go(19 hunks)
⛔ Files not processed due to max files limit (18)
- pkg/cache/memory_concurrency_test.go
- pkg/cache/memory_edge_test.go
- pkg/cache/memory_profile_test.go
- pkg/cache/memory_test.go
- pkg/cache/options.go
- pkg/cache/redis.go
- pkg/crypto/passwords.go
- pkg/health/health.go
- pkg/health/module.go
- pkg/health/service.go
- pkg/health/types.go
- pkg/mysql/errors.go
- pkg/pubsub/memory.go
- pkg/pubsub/options.go
- pkg/pubsub/pubsub.go
- pkg/pubsub/redis.go
- test/e2e/mobile_test.go
- test/e2e/utils_test.go
💤 Files with no reviewable changes (4)
- internal/sms-gateway/modules/messages/service_test.go
- go.mod
- .github/workflows/docker-build.yml
- .github/workflows/docker-publish.yml
✅ Files skipped from review due to trivial changes (1)
- internal/sms-gateway/modules/sse/metrics.go
🚧 Files skipped from review as they are similar to previous changes (34)
- internal/sms-gateway/online/metrics.go
- .gitignore
- internal/sms-gateway/models/module.go
- internal/sms-gateway/modules/events/types.go
- Dockerfile.goreleaser
- internal/worker/config/types.go
- internal/sms-gateway/modules/webhooks/module.go
- internal/worker/executor/metrics.go
- internal/sms-gateway/modules/push/module.go
- internal/sms-gateway/handlers/events/mobile.go
- internal/sms-gateway/handlers/devices/3rdparty.go
- internal/sms-gateway/modules/messages/module.go
- internal/worker/locker/mysql.go
- internal/sms-gateway/models/models.go
- internal/sms-gateway/models/migration.go
- internal/sms-gateway/modules/events/errors.go
- internal/sms-gateway/modules/events/service.go
- internal/sms-gateway/handlers/base/handler.go
- internal/sms-gateway/handlers/settings/3rdparty.go
- internal/sms-gateway/modules/auth/types.go
- internal/sms-gateway/handlers/logs/3rdparty.go
- internal/sms-gateway/pubsub/module.go
- cmd/sms-gateway/main.go
- internal/worker/config/config.go
- internal/sms-gateway/modules/messages/workers.go
- internal/sms-gateway/modules/messages/converters.go
- internal/sms-gateway/handlers/converters/messages.go
- internal/sms-gateway/modules/auth/module.go
- internal/worker/app.go
- pkg/cache/memory_bench_test.go
- internal/sms-gateway/modules/settings/repository.go
- .github/workflows/release.yml
- internal/sms-gateway/cache/factory.go
- internal/sms-gateway/modules/messages/repository_filter.go
🧰 Additional context used
🧠 Learnings (6)
📚 Learning: 2025-09-10T23:45:45.502Z
Learnt from: capcom6
Repo: android-sms-gateway/server PR: 178
File: internal/sms-gateway/online/module.go:14-16
Timestamp: 2025-09-10T23:45:45.502Z
Learning: fx.Decorate in uber-go/fx creates module-scoped decorated dependencies, not global modifications. Using fx.Decorate(func(log *zap.Logger) *zap.Logger { return log.Named("module_name") }) is a correct pattern for creating module-specific loggers that are scoped only to that module's dependency graph.
Applied to files:
internal/sms-gateway/modules/settings/module.gointernal/sms-gateway/handlers/module.gointernal/sms-gateway/modules/metrics/module.gointernal/sms-gateway/modules/events/module.go
📚 Learning: 2025-09-05T08:37:28.077Z
Learnt from: eznix86
Repo: android-sms-gateway/server PR: 172
File: deployments/kubernetes/sms-gateway-server/Chart.yaml:9-10
Timestamp: 2025-09-05T08:37:28.077Z
Learning: In the android-sms-gateway/server project, the Helm chart's appVersion in Chart.yaml is set to "latest" as a placeholder because CI automatically updates it with the actual version from git tags during the release process via the bump-chart-app-version.yaml workflow.
Applied to files:
.goreleaser.yaml
📚 Learning: 2025-07-22T00:31:28.796Z
Learnt from: capcom6
Repo: android-sms-gateway/server PR: 154
File: internal/sms-gateway/modules/sse/service.go:67-79
Timestamp: 2025-07-22T00:31:28.796Z
Learning: In the SSE service Send method (internal/sms-gateway/modules/sse/service.go), blocking on full channels is intentional and acceptable. Timeout control is handled at the calling code level through context, not within the Send method itself. The user prefers this approach over adding a default case that would drop messages when channels are full.
Applied to files:
internal/sms-gateway/modules/sse/service.gointernal/sms-gateway/modules/messages/service.go
📚 Learning: 2025-07-22T00:34:31.896Z
Learnt from: capcom6
Repo: android-sms-gateway/server PR: 154
File: internal/sms-gateway/modules/sse/service.go:89-92
Timestamp: 2025-07-22T00:34:31.896Z
Learning: In Go SSE implementations, when cleaning up connections with buffered channels, avoid closing the data channels directly as this can cause panics if concurrent goroutines attempt to write to them. Instead, use a separate close signal channel to notify writers that the connection should be terminated, allowing the data channels to be garbage collected naturally when the connection is removed.
Applied to files:
internal/sms-gateway/modules/sse/service.gointernal/sms-gateway/modules/sse/errors.go
📚 Learning: 2025-09-10T11:11:43.462Z
Learnt from: capcom6
Repo: android-sms-gateway/server PR: 178
File: pkg/cache/memory.go:59-74
Timestamp: 2025-09-10T11:11:43.462Z
Learning: In pkg/cache/memory.go, the cleanup method removes expired items first, then executes the provided callback function. This means methods like Drain that use cleanup don't need additional expiration filtering since expired items are already removed before the callback runs.
Applied to files:
internal/sms-gateway/online/service.gopkg/cache/memory.go
📚 Learning: 2025-09-10T23:53:42.006Z
Learnt from: capcom6
Repo: android-sms-gateway/server PR: 178
File: pkg/cache/redis.go:63-75
Timestamp: 2025-09-10T23:53:42.006Z
Learning: In pkg/cache/redis.go, the Redis cache implementation uses Redis's native field-level TTL via HExpireAt, meaning expired fields are automatically removed by Redis itself. This differs from the memory implementation where the application tracks expiration times. As a result, operations like Drain that call HGetAll will only return non-expired items since expired fields are already removed by Redis.
Applied to files:
internal/sms-gateway/online/service.gopkg/cache/memory.go
🧬 Code graph analysis (45)
internal/sms-gateway/handlers/middlewares/userauth/userauth.go (1)
internal/sms-gateway/models/models.go (1)
User(18-24)
internal/sms-gateway/modules/push/client.go (5)
internal/sms-gateway/modules/push/fcm/client.go (2)
New(21-27)Client(14-19)internal/sms-gateway/modules/push/service.go (2)
New(43-73)Config(23-30)internal/sms-gateway/modules/push/upstream/client.go (2)
New(29-35)Client(22-27)internal/sms-gateway/modules/push/client/types.go (1)
Client(9-13)internal/sms-gateway/modules/push/types.go (3)
Mode(10-10)ModeFCM(13-13)ModeUpstream(14-14)
internal/sms-gateway/modules/sse/module.go (2)
internal/sms-gateway/app.go (1)
Module(33-58)internal/sms-gateway/modules/sse/service.go (2)
NewService(42-52)Service(21-29)
internal/sms-gateway/modules/auth/cache.go (2)
internal/sms-gateway/models/models.go (1)
User(18-24)internal/sms-gateway/modules/auth/service.go (2)
New(52-67)Config(20-23)
internal/sms-gateway/modules/settings/utils.go (1)
internal/sms-gateway/pubsub/pubsub.go (1)
New(19-51)
internal/sms-gateway/modules/webhooks/repository.go (3)
internal/sms-gateway/modules/devices/repository.go (2)
NewRepository(23-27)Repository(19-21)internal/sms-gateway/modules/messages/repository.go (2)
NewRepository(24-28)Repository(20-22)internal/sms-gateway/modules/webhooks/models.go (1)
Webhook(11-25)
internal/sms-gateway/modules/settings/module.go (1)
internal/sms-gateway/app.go (1)
Module(33-58)
internal/sms-gateway/modules/settings/service.go (1)
internal/sms-gateway/modules/settings/models.go (1)
NewDeviceSettings(19-25)
internal/sms-gateway/modules/sse/config.go (1)
internal/config/config.go (1)
Config(10-20)
internal/sms-gateway/handlers/module.go (1)
internal/sms-gateway/app.go (1)
Module(33-58)
internal/sms-gateway/app.go (16)
internal/config/module.go (1)
Module(23-140)internal/sms-gateway/handlers/module.go (1)
Module(15-41)internal/sms-gateway/modules/db/module.go (1)
Module(16-26)internal/sms-gateway/modules/events/module.go (1)
Module(10-39)internal/sms-gateway/modules/messages/module.go (1)
Module(10-25)internal/sms-gateway/modules/metrics/module.go (1)
Module(9-19)internal/sms-gateway/modules/push/module.go (1)
Module(11-36)internal/sms-gateway/modules/settings/module.go (1)
Module(9-23)internal/sms-gateway/modules/sse/module.go (1)
Module(10-32)internal/sms-gateway/modules/webhooks/module.go (1)
Module(9-20)internal/sms-gateway/pubsub/module.go (1)
Module(11-33)internal/sms-gateway/modules/auth/module.go (1)
Module(10-32)internal/sms-gateway/modules/devices/module.go (1)
Module(8-18)internal/sms-gateway/online/module.go (1)
Module(11-36)internal/sms-gateway/cache/module.go (1)
Module(8-16)internal/worker/app.go (1)
Run(19-28)
internal/sms-gateway/handlers/health.go (1)
pkg/health/types.go (1)
CheckDetail(54-64)
internal/sms-gateway/handlers/webhooks/3rdparty.go (4)
internal/sms-gateway/handlers/base/handler.go (1)
Handler(15-18)internal/sms-gateway/modules/webhooks/models.go (1)
Webhook(11-25)internal/sms-gateway/modules/webhooks/errors.go (1)
IsValidationError(34-36)internal/sms-gateway/modules/webhooks/repository_filter.go (1)
WithExtID(7-11)
internal/sms-gateway/modules/auth/repository.go (1)
internal/sms-gateway/models/models.go (1)
User(18-24)
internal/sms-gateway/handlers/webhooks/mobile.go (5)
internal/sms-gateway/handlers/base/handler.go (1)
Handler(15-18)internal/sms-gateway/modules/webhooks/service.go (1)
Service(28-37)internal/sms-gateway/handlers/events/mobile.go (2)
NewMobileController(19-27)MobileController(13-17)internal/sms-gateway/handlers/messages/mobile.go (2)
NewMobileController(35-43)MobileController(29-33)internal/sms-gateway/handlers/settings/mobile.go (2)
NewMobileController(21-35)MobileController(14-19)
internal/sms-gateway/modules/metrics/module.go (2)
internal/sms-gateway/app.go (1)
Module(33-58)internal/sms-gateway/handlers/module.go (1)
Module(15-41)
internal/sms-gateway/modules/push/fcm/client.go (4)
internal/sms-gateway/modules/push/fcm/errors.go (1)
ErrInitializationFailed(6-6)internal/sms-gateway/modules/push/client/types.go (3)
Client(9-13)Message(15-18)Event(20-23)internal/sms-gateway/modules/push/upstream/client.go (1)
Client(22-27)internal/sms-gateway/modules/push/types.go (1)
Event(17-17)
internal/sms-gateway/modules/push/fcm/utils.go (2)
internal/sms-gateway/modules/push/client/types.go (1)
Event(20-23)internal/sms-gateway/modules/push/types.go (1)
Event(17-17)
internal/config/module.go (8)
internal/sms-gateway/app.go (1)
Module(33-58)internal/sms-gateway/modules/sse/config.go (4)
Option(5-5)Config(7-9)NewConfig(19-27)WithKeepAlivePeriod(33-41)internal/config/config.go (13)
Config(10-20)Default(89-127)HTTP(27-33)Database(44-55)Gateway(22-25)API(35-38)GatewayModePublic(6-6)OpenAPI(40-42)Messages(75-78)Tasks(63-65)SSE(71-73)Cache(80-82)PubSub(84-87)internal/sms-gateway/modules/push/service.go (1)
Config(23-30)internal/worker/config/config.go (3)
Config(9-13)Default(34-66)Tasks(15-19)internal/sms-gateway/modules/messages/config.go (1)
Config(5-8)internal/sms-gateway/pubsub/config.go (1)
Config(4-7)internal/sms-gateway/cache/config.go (1)
Config(4-6)
internal/sms-gateway/modules/db/health.go (1)
pkg/health/types.go (2)
Checks(67-67)Provider(69-75)
internal/config/config.go (2)
internal/worker/config/config.go (3)
Default(34-66)Config(9-13)Tasks(15-19)internal/sms-gateway/pubsub/config.go (1)
Config(4-7)
internal/sms-gateway/handlers/settings/mobile.go (7)
internal/sms-gateway/handlers/base/handler.go (1)
Handler(15-18)internal/sms-gateway/modules/devices/service.go (1)
Service(15-24)internal/sms-gateway/modules/settings/service.go (1)
Service(19-25)internal/sms-gateway/handlers/events/mobile.go (2)
NewMobileController(19-27)MobileController(13-17)internal/sms-gateway/handlers/messages/mobile.go (2)
NewMobileController(35-43)MobileController(29-33)internal/sms-gateway/handlers/webhooks/mobile.go (2)
NewMobileController(21-33)MobileController(15-19)internal/sms-gateway/models/models.go (1)
Device(34-45)
internal/sms-gateway/pubsub/pubsub.go (4)
internal/sms-gateway/pubsub/config.go (1)
Config(4-7)pkg/pubsub/options.go (1)
WithBufferSize(15-19)pkg/pubsub/memory.go (1)
NewMemory(24-38)pkg/pubsub/redis.go (2)
NewRedis(42-78)RedisConfig(15-27)
internal/sms-gateway/modules/webhooks/models.go (2)
internal/sms-gateway/models/models.go (3)
SoftDeletableModel(12-16)User(18-24)Device(34-45)internal/sms-gateway/models/migration.go (1)
Migrate(13-18)
internal/sms-gateway/modules/settings/models.go (2)
internal/sms-gateway/models/models.go (2)
TimedModel(7-10)User(18-24)internal/sms-gateway/models/migration.go (1)
Migrate(13-18)
internal/sms-gateway/handlers/messages/3rdparty.go (8)
internal/sms-gateway/handlers/logs/3rdparty.go (2)
NewThirdPartyController(24-31)ThirdPartyController(20-22)internal/sms-gateway/handlers/settings/3rdparty.go (2)
NewThirdPartyController(35-44)ThirdPartyController(28-33)internal/sms-gateway/handlers/base/handler.go (1)
Handler(15-18)internal/sms-gateway/modules/messages/service.go (1)
EnqueueOptions(22-24)internal/sms-gateway/modules/messages/errors.go (2)
ValidationError(9-9)ErrNoContent(6-6)internal/sms-gateway/modules/messages/repository.go (3)
ErrMultipleMessagesFound(18-18)ErrMessageNotFound(16-16)ErrMessageAlreadyExists(17-17)internal/sms-gateway/modules/devices/repository.go (3)
ErrNotFound(14-14)ErrInvalidFilter(15-15)ErrMoreThanOne(16-16)internal/sms-gateway/modules/devices/errors.go (1)
ErrInvalidUser(6-6)
internal/sms-gateway/modules/devices/service.go (3)
internal/sms-gateway/models/models.go (1)
Device(34-45)internal/sms-gateway/modules/devices/repository_filter.go (4)
SelectFilter(9-9)WithUserID(23-27)WithID(11-15)ActiveWithin(29-33)internal/sms-gateway/modules/devices/repository.go (1)
ErrNotFound(14-14)
internal/sms-gateway/handlers/messages/params.go (1)
internal/sms-gateway/modules/messages/repository_filter.go (3)
SelectFilter(16-23)SelectOptions(51-62)Order(7-7)
internal/sms-gateway/modules/events/module.go (3)
internal/sms-gateway/app.go (1)
Module(33-58)internal/sms-gateway/modules/messages/module.go (1)
Module(10-25)internal/sms-gateway/modules/push/module.go (1)
Module(11-36)
internal/sms-gateway/modules/push/upstream/client.go (2)
internal/sms-gateway/modules/push/fcm/client.go (2)
New(21-27)Client(14-19)internal/sms-gateway/modules/push/client/types.go (2)
Client(9-13)Message(15-18)
internal/sms-gateway/modules/sse/service.go (2)
internal/sms-gateway/modules/sse/errors.go (1)
ErrNoConnection(6-6)internal/sms-gateway/modules/sse/metrics.go (2)
ErrorTypeMarshalError(22-22)ErrorTypeWriteFailure(21-21)
internal/sms-gateway/modules/messages/cache.go (2)
internal/sms-gateway/modules/messages/domain.go (1)
MessageStateOut(38-44)pkg/cache/options.go (2)
WithTTL(20-31)AndSetTTL(65-69)
internal/sms-gateway/modules/push/service.go (2)
internal/sms-gateway/modules/push/client/types.go (3)
Client(9-13)Event(20-23)Message(15-18)internal/sms-gateway/modules/push/types.go (1)
Event(17-17)
internal/sms-gateway/modules/webhooks/errors.go (2)
internal/sms-gateway/pubsub/pubsub.go (1)
New(19-51)internal/sms-gateway/modules/messages/errors.go (1)
ValidationError(9-9)
internal/sms-gateway/modules/messages/repository.go (2)
internal/sms-gateway/modules/messages/repository_filter.go (3)
SelectFilter(16-23)SelectOptions(51-62)Order(7-7)internal/sms-gateway/modules/messages/models.go (3)
Message(36-56)ProcessingStatePending(17-17)MessageRecipient(139-145)
internal/sms-gateway/modules/devices/repository.go (4)
internal/sms-gateway/handlers/middlewares/deviceauth/deviceauth.go (1)
New(20-44)internal/sms-gateway/models/models.go (1)
Device(34-45)internal/sms-gateway/modules/messages/repository.go (1)
Repository(20-22)internal/sms-gateway/modules/webhooks/repository.go (1)
Repository(8-10)
pkg/cache/memory.go (2)
pkg/cache/options.go (3)
GetOption(49-49)AndDelete(89-93)Option(6-6)pkg/cache/cache.go (1)
Cache(5-40)
internal/sms-gateway/handlers/mobile.go (1)
internal/sms-gateway/models/models.go (2)
Device(34-45)User(18-24)
internal/sms-gateway/modules/webhooks/service.go (1)
internal/sms-gateway/modules/webhooks/errors.go (1)
ErrInvalidEvent(9-9)
internal/sms-gateway/handlers/messages/mobile.go (5)
internal/sms-gateway/handlers/events/mobile.go (2)
NewMobileController(19-27)MobileController(13-17)internal/sms-gateway/handlers/settings/mobile.go (2)
NewMobileController(21-35)MobileController(14-19)internal/sms-gateway/handlers/webhooks/mobile.go (2)
NewMobileController(21-33)MobileController(15-19)internal/sms-gateway/handlers/base/handler.go (1)
Handler(15-18)internal/sms-gateway/models/models.go (1)
Device(34-45)
internal/sms-gateway/modules/messages/service.go (7)
internal/sms-gateway/modules/messages/repository_filter.go (3)
Order(7-7)SelectFilter(16-23)SelectOptions(51-62)internal/sms-gateway/modules/messages/domain.go (4)
MessageOut(25-29)MessageStateIn(31-36)MessageStateOut(38-44)MessageIn(9-23)internal/sms-gateway/models/models.go (2)
Device(34-45)User(18-24)internal/sms-gateway/modules/messages/models.go (6)
MessageState(157-162)ProcessingState(13-13)ProcessingStatePending(17-17)MessageRecipient(139-145)Message(36-56)NewMessage(58-83)internal/sms-gateway/modules/messages/repository.go (1)
ErrMessageNotFound(16-16)internal/sms-gateway/modules/events/events.go (2)
NewMessageEnqueuedEvent(9-11)NewMessagesExportRequestedEvent(17-25)internal/sms-gateway/modules/messages/errors.go (2)
ErrNoContent(6-6)ValidationError(9-9)
internal/sms-gateway/modules/auth/service.go (5)
internal/sms-gateway/online/service.go (2)
New(28-37)Service(14-17)internal/sms-gateway/modules/auth/types.go (1)
OneTimeCode(15-18)internal/sms-gateway/models/models.go (4)
User(18-24)NewUser(26-32)Device(34-45)NewDevice(47-53)pkg/crypto/passwords.go (2)
MakeBCryptHash(14-20)CompareBCryptHash(22-27)internal/sms-gateway/modules/auth/errors.go (1)
ErrAuthorizationFailed(6-6)
internal/sms-gateway/modules/messages/models.go (1)
internal/sms-gateway/models/models.go (1)
SoftDeletableModel(12-16)
internal/sms-gateway/modules/push/types.go (1)
internal/sms-gateway/modules/push/client/types.go (1)
Event(20-23)
internal/sms-gateway/modules/messages/errors.go (1)
internal/sms-gateway/modules/webhooks/errors.go (1)
ValidationError(12-16)
🪛 checkmake (0.2.2)
Makefile
[warning] 10-10: Missing required phony target "all"
(minphony)
[warning] 10-10: Missing required phony target "clean"
(minphony)
[warning] 10-10: Missing required phony target "test"
(minphony)
[warning] 15-15: Target "all" should be declared PHONY.
(phonydeclared)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
- GitHub Check: Benchmark
- GitHub Check: E2E
- GitHub Check: Analyze (go)
|
Worker deployed... |
bd00981 to
23abfa0
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 4
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (2)
internal/sms-gateway/modules/sse/service.go (2)
107-117: Critical: Metric leak in Close() method.The
Close()method closes all connections and removes them from the map but never callsDecrementActiveConnections(). This leaves the active connections metric permanently inflated by the number of open connections at shutdown.When
handleStreamgoroutines subsequently call the deferredremoveConnection(), they won't find their connections in the map (becauseClose()already deleted them) and therefore won't decrement the metric either.Apply this diff to fix the metric leak:
func (s *Service) Close(_ context.Context) error { s.mu.Lock() defer s.mu.Unlock() for deviceID, connections := range s.connections { for _, conn := range connections { close(conn.closeSignal) + // Decrement metric for each connection being closed + s.metrics.DecrementActiveConnections() } delete(s.connections, deviceID) } return nil }
220-244: Major: Metric decrement outside connection-found check.The
DecrementActiveConnections()call at line 239 is outside the innerif conn.id == connIDcheck. This means:
- If
removeConnectionis called with aconnIDthat doesn't exist (butdeviceIDdoes), the metric is still decremented → incorrect count- If the connection was already removed by
Close(), the metric isn't decremented because thedeviceIDdoesn't exist anymore → combined with theClose()issue, this creates a metric leakApply this diff to decrement only when a connection is actually found and removed:
func (s *Service) removeConnection(deviceID, connID string) { s.mu.Lock() defer s.mu.Unlock() if connections, exists := s.connections[deviceID]; exists { for i, conn := range connections { if conn.id == connID { close(conn.closeSignal) s.connections[deviceID] = append(connections[:i], connections[i+1:]...) + // Decrement active connections metric + s.metrics.DecrementActiveConnections() s.logger.Info( "Removing SSE connection", zap.String("device_id", deviceID), zap.String("connection_id", connID), ) break } } - // Decrement active connections metric - s.metrics.DecrementActiveConnections() - if len(s.connections[deviceID]) == 0 { delete(s.connections, deviceID) } } }
♻️ Duplicate comments (12)
internal/version/version.go (1)
7-7: Complete the grammatical fix from the previous review.The past review noted two grammatical issues, but only one was addressed. While "these information" was correctly changed to "This information", the phrase "when build" still needs correction to "when building" or "during build".
Apply this diff to complete the fix:
-// This information will be collected when build, by `-ldflags "-X main.appVersion=0.1"`. +// This information will be collected during build, by `-ldflags "-X main.appVersion=0.1"`..golangci.yml (1)
347-350: Remove dead configuration for disabled linter.The
inamedparamlinter is commented out (line 127), but its settings are still configured here. This configuration has no effect and should be removed to keep the config clean.Apply this diff to remove the orphaned settings:
- inamedparam: - # Skips check for interface methods with only a single parameter. - # Default: false - skip-single-param: true -Alternatively, if you intend to enable this linter in the future, uncomment line 127 and keep this configuration block.
internal/sms-gateway/modules/messages/errors.go (1)
9-12: Rename ValidationError to avoid collision with webhooks.ValidationErrorThis package’s
ValidationError(string type) still collides by name withwebhooks.ValidationError(struct). When both packages are imported, this increases ambiguity and often forces aliasing.Consider renaming to something more specific like
MessageValidationError(and updating its uses) to keep error types clearly distinguishable..github/workflows/pr.yml (1)
92-92: Pin S3 upload action to a specific release tag for supply-chain security.Line 92 references
capcom6/upload-s3-action@master, which uses the unpinned master branch. This creates supply-chain risk and makes CI/CD non-reproducible. A past review already flagged this concern—pin to a specific release tag.- uses: capcom6/upload-s3-action@master + uses: capcom6/upload-s3-action@v1(Replace
v1with the actual stable version after verifying the action's releases on GitHub.)internal/sms-gateway/modules/push/upstream/client.go (1)
62-62: Remove the unnecessary type conversion.As previously noted,
UpstreamPushRequestis a type alias for[]smsgateway.PushNotification, so the explicit conversion is redundant.Apply this diff:
- payloadBytes, err := json.Marshal(smsgateway.UpstreamPushRequest(payload)) //nolint:unconvert //type checking + payloadBytes, err := json.Marshal(payload)internal/sms-gateway/modules/auth/service.go (2)
52-66: Either handle or explicitly document the ignorednanoid.Standarderror
nanoid.Standard(idLen)returns an error which is currently discarded:const idLen = 21 idgen, _ := nanoid.Standard(idLen)Given a compile‑time constant size, this is probably safe, but it’s opaque to readers and was already called out in an earlier review.
Consider either handling the error or adding a short comment to justify ignoring it, e.g.:
- const idLen = 21 - idgen, _ := nanoid.Standard(idLen) + const idLen = 21 + // nanoid.Standard with size 21 cannot fail for the version we use. + idgen, _ := nanoid.Standard(idLen)
99-121: Don’t return non‑persistedUser/Deviceobjects when inserts fail
RegisterUserandRegisterDevicestill return constructed objects even when insertion fails:user := models.NewUser(login, passwordHash) if err = s.users.Insert(user); err != nil { return user, fmt.Errorf("failed to create user: %w", err) } device := models.NewDevice(name, pushToken) if err := s.devicesSvc.Insert(user.ID, device); err != nil { return device, fmt.Errorf("failed to create device: %w", err) }This is the same pattern previously flagged: callers might accidentally proceed with a user/device that doesn’t exist in storage.
Safer to return
nilon any persistence error:func (s *Service) RegisterUser(login, password string) (*models.User, error) { @@ user := models.NewUser(login, passwordHash) - if err = s.users.Insert(user); err != nil { - return user, fmt.Errorf("failed to create user: %w", err) - } - return user, nil + if err = s.users.Insert(user); err != nil { + return nil, fmt.Errorf("failed to create user: %w", err) + } + return user, nil } func (s *Service) RegisterDevice(user *models.User, name, pushToken *string) (*models.Device, error) { device := models.NewDevice(name, pushToken) - if err := s.devicesSvc.Insert(user.ID, device); err != nil { - return device, fmt.Errorf("failed to create device: %w", err) - } - return device, nil + if err := s.devicesSvc.Insert(user.ID, device); err != nil { + return nil, fmt.Errorf("failed to create device: %w", err) + } + return device, nil }internal/sms-gateway/modules/settings/module.go (1)
15-18: Movefx.PrivatebeforenewRepositoryto make it private.In uber-go/fx,
fx.Privateonly affects constructors that follow it in the samefx.Providecall. SincenewRepositoryis listed beforefx.Private, it remains accessible to the wider dependency graph. IfnewRepositoryshould be module-private, reorder to:fx.Provide( + fx.Private, newRepository, - fx.Private, ),If broader visibility is intentional, remove the unused
fx.Private.internal/sms-gateway/modules/events/module.go (1)
10-39: KeepnewMetricsprivate by reorderingfx.Private.
fx.Privateonly applies to constructors that follow it in the samefx.Providecall; placing it afternewMetricsmakes the metrics provider visible to the wider app graph, changing the module’s surface area. To keepnewMetricsprivate to the events module, swap the order:- fx.Provide(newMetrics, fx.Private), + fx.Provide(fx.Private, newMetrics),The rest of the
Module()wiring (includinglog.Named("events")and the lifecycle hook) looks good and matches the project’s module patterns. Based on learningsIn Uber Fx, does `fx.Private` only affect constructors that follow it in the same `fx.Provide` call, and is `fx.Provide(fx.Private, newMetrics)` the correct way to keep `newMetrics` private to the module?internal/sms-gateway/handlers/mobile.go (1)
275-276: Don’t silently ignore nanoid.Standard errorsThe error from
nanoid.Standard(idGenSize)is discarded:const idGenSize = 21 idGen, _ := nanoid.Standard(idGenSize)Even if size 21 is effectively guaranteed to succeed, ignoring the error without comment makes future debugging harder.
Consider either handling or documenting it, e.g.:
-const idGenSize = 21 -idGen, _ := nanoid.Standard(idGenSize) +const idGenSize = 21 +idGen, err := nanoid.Standard(idGenSize) +if err != nil { + params.Logger.Fatal("failed to initialize ID generator", zap.Error(err)) +}or, if you’re confident it can’t fail:
const idGenSize = 21 - idGen, _ := nanoid.Standard(idGenSize) + // nanoid.Standard with size 21 never returns an error + idGen, _ := nanoid.Standard(idGenSize)internal/sms-gateway/handlers/messages/params.go (1)
24-27: Fix date-range validation to compare parsed times, not strings
Validatestill uses a lexicographical string comparison:if p.StartDate != "" && p.EndDate != "" && p.StartDate > p.EndDate { return errors.New("`from` date must be before `to` date") }RFC3339 strings with different offsets can compare incorrectly (e.g.,
"2025-11-10T12:00:00+02:00"sorts after"2025-11-10T11:00:00+00:00"even though it’s earlier in UTC), so valid ranges may be rejected.Consider parsing both timestamps and comparing
time.Timevalues:func (p *thirdPartyGetQueryParams) Validate() error { - if p.StartDate != "" && p.EndDate != "" && p.StartDate > p.EndDate { - return errors.New("`from` date must be before `to` date") //nolint:err113 // won't be used directly - } + if p.StartDate != "" && p.EndDate != "" { + from, err := time.Parse(time.RFC3339, p.StartDate) + if err != nil { + return errors.New("`from` date must be before `to` date") //nolint:err113 // won't be used directly + } + + to, err := time.Parse(time.RFC3339, p.EndDate) + if err != nil { + return errors.New("`from` date must be before `to` date") //nolint:err113 // won't be used directly + } + + if from.After(to) { + return errors.New("`from` date must be before `to` date") //nolint:err113 // won't be used directly + } + } return nil }internal/sms-gateway/modules/messages/service.go (1)
220-241: Address TTL integer overflow risk in prepareMessageTTL handling still does an unchecked conversion from user-controlled
*message.TTL(uint64) totime.Duration:if message.TTL != nil && *message.TTL > 0 { //nolint:gosec // not a problem validUntil = anys.AsPointer( time.Now().Add(time.Duration(*message.TTL) * time.Second), ) }For sufficiently large TTL values,
time.Duration(*message.TTL) * time.Secondcan overflowint64, leading to incorrectvalidUntilvalues. This was previously flagged by gosec.Recommend adding an explicit upper bound before the conversion and returning a validation error when exceeded, e.g.:
func (s *Service) prepareMessage(device models.Device, message MessageIn, opts EnqueueOptions) (*Message, error) { @@ - validUntil := message.ValidUntil - if message.TTL != nil && *message.TTL > 0 { - //nolint:gosec // not a problem - validUntil = anys.AsPointer( - time.Now().Add(time.Duration(*message.TTL) * time.Second), - ) - } + validUntil := message.ValidUntil + if message.TTL != nil && *message.TTL > 0 { + const maxTTLSeconds uint64 = 315360000 // ~10 years + if *message.TTL > maxTTLSeconds { + return nil, ValidationError("ttl exceeds maximum allowed value") + } + + validUntil = anys.AsPointer( + time.Now().Add(time.Duration(*message.TTL) * time.Second), + ) + }This both eliminates the overflow and surfaces an appropriate 400 via your
errorHandler’sValidationErrormapping.
🧹 Nitpick comments (17)
internal/sms-gateway/modules/db/health.go (2)
20-21: Optional: Remove redundant explicit initialization.The
atomic.Int64{}initialization is redundant since Go automatically zero-initializes struct fields.- - failedPings: atomic.Int64{},
31-32: Prefer empty map over nil for consistency and idiom.Returning
nil, nilis non-idiomatic when the intent is "no checks to report." The idiomatic Go approach is to return an empty map (healthmod.Checks{}) rather thannil, which also maintains consistency withReadyProbethat returns a non-nil map.Apply this diff:
-func (h *health) LiveProbe(_ context.Context) (healthmod.Checks, error) { - return nil, nil //nolint:nilnil // empty result +func (h *health) LiveProbe(_ context.Context) (healthmod.Checks, error) { + return healthmod.Checks{}, nil }-func (h *health) StartedProbe(_ context.Context) (healthmod.Checks, error) { - return nil, nil //nolint:nilnil // empty result +func (h *health) StartedProbe(_ context.Context) (healthmod.Checks, error) { + return healthmod.Checks{}, nil }Also applies to: 58-59
.github/workflows/release.yml (1)
17-17: Align checkout action version with pr.yml for consistency.Line 17 uses
actions/checkout@v5, while pr.yml usesactions/checkout@v4. Keeping both workflows on the same major version ensures consistent behavior and reduces maintenance overhead. A past review also noted this version mismatch—consider aligning to a single version across both workflows.- name: Checkout - uses: actions/checkout@v5 + uses: actions/checkout@v4 with: fetch-depth: 0internal/sms-gateway/handlers/middlewares/userauth/userauth.go (1)
118-124: LGTM! Nil check properly prevents dereference panic.The nil check at line 119 correctly addresses the previous critical issue. Dereferencing the pointer when passing to the handler (line 123) provides immutability, which is a good design choice.
Minor consistency note: Line 120 uses
fiber.NewError(fiber.StatusUnauthorized, "Unauthorized")while the rest of the file usesfiber.ErrUnauthorized(lines 31, 41, 50, 77, 108). Consider using the predefined constant for consistency.Apply this diff for consistency:
- return fiber.NewError(fiber.StatusUnauthorized, "Unauthorized") + return fiber.ErrUnauthorizedinternal/sms-gateway/modules/push/fcm/client.go (1)
24-25: Consider removing explicit zero-value initializations.Lines 24-25 explicitly initialize
clienttonilandmuxtosync.Mutex{}, which are their zero values. While explicit initialization can improve clarity, Go's zero-value semantics make these unnecessary.Apply this diff if you prefer more idiomatic Go:
func New(options map[string]string) (*Client, error) { return &Client{ options: options, - client: nil, - mux: sync.Mutex{}, }, nil }internal/sms-gateway/modules/sse/service.go (1)
46-46: Redundant sync.RWMutex initialization.The zero value of
sync.RWMutexis valid and ready to use without explicit initialization.Apply this diff to remove the redundant initialization:
- mu: sync.RWMutex{}, + mu: sync.RWMutex{}, // or just remove this line entirelyActually, you can remove the line entirely:
return &Service{ config: config, - mu: sync.RWMutex{}, connections: make(map[string][]*sseConnection),internal/sms-gateway/modules/auth/service.go (3)
69-97:GenerateUserCodelogic is fine, butfor range 3depends on Go 1.22+The 3‑attempt loop:
for range 3 { // ... }relies on the “range over integer” feature, which only exists in newer Go versions. If your
go.modor build matrix still targets pre‑1.22, this won’t compile.To avoid coupling this code to a minimum Go version, you can switch to a classic counted loop:
- const bytesLen = 3 - const maxCode = 1000000 - b := make([]byte, bytesLen) + const ( + bytesLen = 3 + maxCode = 1000000 + maxAttempts = 3 + ) + b := make([]byte, bytesLen) validUntil := time.Now().Add(codeTTL) - for range 3 { + for i := 0; i < maxAttempts; i++ { if _, err = rand.Read(b); err != nil { continue } num := (int(b[0]) << 16) | (int(b[1]) << 8) | int(b[2]) //nolint:mnd //bitshift code = fmt.Sprintf("%06d", num%maxCode) if err = s.codesCache.SetOrFail(code, userID, cache.WithValidUntil(validUntil)); err != nil { continue } break }The rest of the function (code generation, retries, and error wrapping) looks solid.
127-155: Auth registration/device flows look good; consider context wiring for SetOnlineUsing
ErrAuthorizationFailedinAuthorizeRegistrationand wrapping device lookup errors with it inAuthorizeDeviceis a nice improvement over generic errors.One minor point: in
AuthorizeDeviceyou spawn a goroutine withcontext.Background():go func(id string) { const timeout = 5 * time.Second ctx, cancel := context.WithTimeout(context.Background(), timeout) defer cancel() s.onlineSvc.SetOnline(ctx, id) }(device.ID)If you ever need tighter lifecycle control (e.g., tying this work to the request or service lifetime), consider passing in a parent context instead of
context.Background(). Not urgent, but it will make graceful shutdown semantics clearer.
179-215: Verify ID vs “login” semantics in ChangePassword & cache invalidationIn
ChangePassword:func (s *Service) ChangePassword(userID string, currentPassword string, newPassword string) error { user, err := s.users.GetByLogin(userID) // ... if updErr := s.users.UpdatePassword(userID, newHash); updErr != nil { ... } // Invalidate cache if delErr := s.usersCache.Delete(userID, currentPassword); delErr != nil { ... } }and from the model snippet (
internal/sms-gateway/models/models.go) theUserstruct currently exposesIDandPasswordHashonly. Combined withGetByLogindoingWhere("id = ?", login), it looks like “login” and “ID” may be the same logical field now.If that’s intentional, this is coherent but a bit confusing (using
userIDwithGetByLogin). If there is a separate logical login/username, then:
GetByLoginprobably shouldn’t filter on"id", and/orChangePasswordshould be usingGetByIDand passing the correct key tousersCache.Delete.Either way, worth double‑checking that:
GetByLoginis querying the right column.- The key used in
usersCache.Set(username, password, *user)matches the key you pass intoDelete(userID, currentPassword)so that password changes actually invalidate the cache entry.internal/worker/executor/service.go (1)
100-109: Clearer lock error messages; consider avoiding duplicatenamefieldThe updated messages (“failed to acquire/release lock”) are more descriptive. Note that
loggeris already scoped withzap.String("name", task.Name()), so passingzap.String("name", task.Name())again is slightly redundant; you could rely on the scoped logger and only addzap.Error(err)here.internal/sms-gateway/modules/webhooks/errors.go (1)
18-20: IsValidationError is correct but can follow the canonicalerrors.AspatternThe new
Error()formatting is clearer, and usingerrors.AsmakesIsValidationErrorrobust to wrapping. For style and to avoid the extra allocation fromnew(ValidationError), you might use the usual pattern:func IsValidationError(err error) bool { var target ValidationError return errors.As(err, &target) }This is equivalent for your current value-based
ValidationErrorusage and follows the standard library examples.Also applies to: 34-36
internal/sms-gateway/modules/settings/utils.go (1)
62-88: Good use of ErrInvalidField in map-shape validationWrapping structural mismatches in
filterMapandappendMapwith%wandErrInvalidFieldmakes it easy for callers to distinguish schema errors from other failures. If you want slightly more idiomatic formatting, you could switch the manual quotes to%qfor field names, but the current messages are already clear.Also applies to: 90-124
internal/sms-gateway/modules/devices/service.go (1)
80-107:math/rand/v2support is confirmed; error wrapping remains an optional improvement.Your Go version (1.24.3) is well above the Go 1.22 minimum where math/rand/v2 was introduced, so the
rand.IntNusage is fully compatible with no concerns.The filter construction logic and random device selection are sound. The optional error wrapping suggestion still applies if you'd like to add context to repository errors:
- devices, err := s.devices.Select(filter...) - if err != nil { - return nil, err - } + devices, err := s.devices.Select(filter...) + if err != nil { + return nil, fmt.Errorf("failed to select devices: %w", err) + }internal/sms-gateway/handlers/messages/mobile.go (1)
35-43: Consider aligning constructor pattern with other handlers.This constructor still uses
mobileControllerParamswithfx.In, whilewebhooks/mobile.goandsettings/mobile.gohave moved to explicit parameters (webhooksSvc, logger, validator). For consistency across the codebase, consider refactoring to match the explicit parameter pattern.internal/sms-gateway/handlers/webhooks/3rdparty.go (1)
32-40: Consider aligning constructor pattern.Similar to
messages/mobile.go, this constructor still usesthirdPartyControllerParamswithfx.In, while several other handlers (webhooks/mobile.go,settings/mobile.go) have moved to explicit parameters. Consider refactoring for consistency.internal/sms-gateway/handlers/mobile.go (1)
95-101: Registration handler’s validation and error mapping are mostly solidUsing a pointer
MobileRegisterRequestand returningfiber.NewError(fiber.StatusBadRequest, err.Error())for body/validation failures correctly yields a 400 client error. User/device creation failures are propagated as wrapped Go errors; assuming a global Fiber error handler translates these to 500s, this is acceptable, though you may want to add logging and/or convert them tofiber.NewError(500, "...")for more consistent client messaging.Also applies to: 104-127
internal/sms-gateway/modules/messages/service.go (1)
176-218: Enqueue flow is solid; consider clarifying state-on-error semanticsThe enqueue path:
- Prepares a
MessageviaprepareMessage.- Builds an initial
MessageStateOutusingmsg.ExtIDand recipient states.- Inserts the message, caches the persisted state, increments metrics, and asynchronously notifies the device via
eventsSvc.Notify.Returning a non-nil
statealongsideinsErr:if insErr := s.messages.Insert(msg); insErr != nil { return state, insErr }is unconventional in Go; callers must be disciplined to ignore
statewhenerr != nil. Current 3rd‑party handler dropsstateon error, but if future call sites use it, this could be surprising. Either documenting this behavior inEnqueue’s comment or switching toreturn nil, insErrwould make the contract clearer.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
⛔ Files ignored due to path filters (1)
go.sumis excluded by!**/*.sum
📒 Files selected for processing (107)
.github/workflows/docker-build.yml(0 hunks).github/workflows/docker-publish.yml(0 hunks).github/workflows/go.yml(3 hunks).github/workflows/pr.yml(1 hunks).github/workflows/release.yml(2 hunks).gitignore(1 hunks).golangci.yml(1 hunks).goreleaser.yaml(1 hunks)Dockerfile.goreleaser(1 hunks)Makefile(1 hunks)cmd/sms-gateway/main.go(2 hunks)go.mod(0 hunks)internal/config/config.go(3 hunks)internal/config/module.go(1 hunks)internal/sms-gateway/app.go(2 hunks)internal/sms-gateway/cache/errors.go(1 hunks)internal/sms-gateway/cache/factory.go(2 hunks)internal/sms-gateway/handlers/base/handler.go(1 hunks)internal/sms-gateway/handlers/base/handler_test.go(5 hunks)internal/sms-gateway/handlers/converters/messages.go(1 hunks)internal/sms-gateway/handlers/devices/3rdparty.go(4 hunks)internal/sms-gateway/handlers/events/mobile.go(1 hunks)internal/sms-gateway/handlers/health.go(2 hunks)internal/sms-gateway/handlers/logs/3rdparty.go(2 hunks)internal/sms-gateway/handlers/messages/3rdparty.go(8 hunks)internal/sms-gateway/handlers/messages/mobile.go(3 hunks)internal/sms-gateway/handlers/messages/params.go(3 hunks)internal/sms-gateway/handlers/middlewares/userauth/userauth.go(2 hunks)internal/sms-gateway/handlers/mobile.go(8 hunks)internal/sms-gateway/handlers/module.go(1 hunks)internal/sms-gateway/handlers/root.go(1 hunks)internal/sms-gateway/handlers/settings/3rdparty.go(4 hunks)internal/sms-gateway/handlers/settings/mobile.go(2 hunks)internal/sms-gateway/handlers/upstream.go(4 hunks)internal/sms-gateway/handlers/webhooks/3rdparty.go(4 hunks)internal/sms-gateway/handlers/webhooks/mobile.go(2 hunks)internal/sms-gateway/models/migration.go(2 hunks)internal/sms-gateway/models/models.go(2 hunks)internal/sms-gateway/models/module.go(1 hunks)internal/sms-gateway/modules/auth/cache.go(1 hunks)internal/sms-gateway/modules/auth/errors.go(1 hunks)internal/sms-gateway/modules/auth/module.go(1 hunks)internal/sms-gateway/modules/auth/repository.go(1 hunks)internal/sms-gateway/modules/auth/service.go(7 hunks)internal/sms-gateway/modules/auth/types.go(1 hunks)internal/sms-gateway/modules/db/health.go(3 hunks)internal/sms-gateway/modules/db/module.go(2 hunks)internal/sms-gateway/modules/devices/repository.go(5 hunks)internal/sms-gateway/modules/devices/repository_filter.go(1 hunks)internal/sms-gateway/modules/devices/service.go(6 hunks)internal/sms-gateway/modules/events/errors.go(1 hunks)internal/sms-gateway/modules/events/metrics.go(3 hunks)internal/sms-gateway/modules/events/module.go(1 hunks)internal/sms-gateway/modules/events/service.go(7 hunks)internal/sms-gateway/modules/events/types.go(2 hunks)internal/sms-gateway/modules/messages/cache.go(2 hunks)internal/sms-gateway/modules/messages/converters.go(1 hunks)internal/sms-gateway/modules/messages/errors.go(1 hunks)internal/sms-gateway/modules/messages/models.go(7 hunks)internal/sms-gateway/modules/messages/module.go(1 hunks)internal/sms-gateway/modules/messages/repository.go(7 hunks)internal/sms-gateway/modules/messages/repository_filter.go(2 hunks)internal/sms-gateway/modules/messages/service.go(7 hunks)internal/sms-gateway/modules/messages/service_test.go(0 hunks)internal/sms-gateway/modules/messages/workers.go(5 hunks)internal/sms-gateway/modules/metrics/handler.go(1 hunks)internal/sms-gateway/modules/metrics/module.go(1 hunks)internal/sms-gateway/modules/push/client.go(1 hunks)internal/sms-gateway/modules/push/client/types.go(1 hunks)internal/sms-gateway/modules/push/fcm/client.go(4 hunks)internal/sms-gateway/modules/push/fcm/errors.go(1 hunks)internal/sms-gateway/modules/push/fcm/utils.go(1 hunks)internal/sms-gateway/modules/push/module.go(2 hunks)internal/sms-gateway/modules/push/service.go(10 hunks)internal/sms-gateway/modules/push/types.go(3 hunks)internal/sms-gateway/modules/push/upstream/client.go(4 hunks)internal/sms-gateway/modules/settings/models.go(1 hunks)internal/sms-gateway/modules/settings/module.go(1 hunks)internal/sms-gateway/modules/settings/repository.go(5 hunks)internal/sms-gateway/modules/settings/service.go(3 hunks)internal/sms-gateway/modules/settings/utils.go(4 hunks)internal/sms-gateway/modules/sse/config.go(2 hunks)internal/sms-gateway/modules/sse/errors.go(1 hunks)internal/sms-gateway/modules/sse/metrics.go(3 hunks)internal/sms-gateway/modules/sse/module.go(1 hunks)internal/sms-gateway/modules/sse/service.go(8 hunks)internal/sms-gateway/modules/webhooks/errors.go(3 hunks)internal/sms-gateway/modules/webhooks/models.go(1 hunks)internal/sms-gateway/modules/webhooks/module.go(1 hunks)internal/sms-gateway/modules/webhooks/repository.go(2 hunks)internal/sms-gateway/modules/webhooks/repository_filter.go(1 hunks)internal/sms-gateway/modules/webhooks/service.go(5 hunks)internal/sms-gateway/online/metrics.go(5 hunks)internal/sms-gateway/online/service.go(5 hunks)internal/sms-gateway/openapi/docs.go(6 hunks)internal/sms-gateway/pubsub/module.go(2 hunks)internal/sms-gateway/pubsub/pubsub.go(2 hunks)internal/version/version.go(1 hunks)internal/worker/app.go(1 hunks)internal/worker/config/config.go(1 hunks)internal/worker/config/types.go(2 hunks)internal/worker/executor/metrics.go(1 hunks)internal/worker/executor/service.go(3 hunks)internal/worker/locker/mysql.go(4 hunks)pkg/cache/errors.go(1 hunks)pkg/cache/memory.go(12 hunks)pkg/cache/memory_bench_test.go(19 hunks)
⛔ Files not processed due to max files limit (18)
- pkg/cache/memory_concurrency_test.go
- pkg/cache/memory_edge_test.go
- pkg/cache/memory_profile_test.go
- pkg/cache/memory_test.go
- pkg/cache/options.go
- pkg/cache/redis.go
- pkg/crypto/passwords.go
- pkg/health/health.go
- pkg/health/module.go
- pkg/health/service.go
- pkg/health/types.go
- pkg/mysql/errors.go
- pkg/pubsub/memory.go
- pkg/pubsub/options.go
- pkg/pubsub/pubsub.go
- pkg/pubsub/redis.go
- test/e2e/mobile_test.go
- test/e2e/utils_test.go
💤 Files with no reviewable changes (4)
- go.mod
- internal/sms-gateway/modules/messages/service_test.go
- .github/workflows/docker-build.yml
- .github/workflows/docker-publish.yml
✅ Files skipped from review due to trivial changes (1)
- internal/sms-gateway/handlers/root.go
🚧 Files skipped from review as they are similar to previous changes (45)
- internal/sms-gateway/pubsub/module.go
- internal/sms-gateway/modules/webhooks/repository_filter.go
- internal/sms-gateway/cache/errors.go
- internal/sms-gateway/modules/push/client.go
- internal/sms-gateway/handlers/upstream.go
- cmd/sms-gateway/main.go
- internal/sms-gateway/modules/webhooks/module.go
- internal/sms-gateway/handlers/logs/3rdparty.go
- internal/sms-gateway/modules/push/fcm/errors.go
- internal/sms-gateway/handlers/converters/messages.go
- .github/workflows/go.yml
- internal/sms-gateway/modules/events/errors.go
- internal/sms-gateway/modules/devices/repository_filter.go
- internal/sms-gateway/models/migration.go
- internal/worker/config/types.go
- internal/sms-gateway/handlers/events/mobile.go
- internal/sms-gateway/modules/messages/workers.go
- internal/worker/locker/mysql.go
- .goreleaser.yaml
- internal/sms-gateway/modules/auth/types.go
- internal/sms-gateway/modules/webhooks/repository.go
- internal/sms-gateway/modules/sse/metrics.go
- pkg/cache/errors.go
- internal/sms-gateway/modules/settings/service.go
- internal/sms-gateway/modules/auth/module.go
- internal/sms-gateway/modules/sse/config.go
- internal/sms-gateway/modules/messages/module.go
- internal/sms-gateway/modules/sse/module.go
- internal/sms-gateway/models/models.go
- pkg/cache/memory_bench_test.go
- internal/sms-gateway/modules/push/fcm/utils.go
- .gitignore
- internal/sms-gateway/modules/auth/cache.go
- internal/sms-gateway/cache/factory.go
- internal/sms-gateway/modules/webhooks/models.go
- internal/sms-gateway/modules/metrics/handler.go
- internal/sms-gateway/modules/auth/errors.go
- internal/sms-gateway/handlers/settings/3rdparty.go
- internal/sms-gateway/modules/events/types.go
- internal/sms-gateway/modules/messages/repository_filter.go
- internal/sms-gateway/handlers/base/handler.go
- internal/sms-gateway/handlers/health.go
- internal/sms-gateway/handlers/module.go
- internal/sms-gateway/modules/metrics/module.go
- internal/sms-gateway/modules/db/module.go
🧰 Additional context used
🧠 Learnings (5)
📚 Learning: 2025-09-10T11:11:43.462Z
Learnt from: capcom6
Repo: android-sms-gateway/server PR: 178
File: pkg/cache/memory.go:59-74
Timestamp: 2025-09-10T11:11:43.462Z
Learning: In pkg/cache/memory.go, the cleanup method removes expired items first, then executes the provided callback function. This means methods like Drain that use cleanup don't need additional expiration filtering since expired items are already removed before the callback runs.
Applied to files:
internal/sms-gateway/online/service.gopkg/cache/memory.go
📚 Learning: 2025-09-10T23:53:42.006Z
Learnt from: capcom6
Repo: android-sms-gateway/server PR: 178
File: pkg/cache/redis.go:63-75
Timestamp: 2025-09-10T23:53:42.006Z
Learning: In pkg/cache/redis.go, the Redis cache implementation uses Redis's native field-level TTL via HExpireAt, meaning expired fields are automatically removed by Redis itself. This differs from the memory implementation where the application tracks expiration times. As a result, operations like Drain that call HGetAll will only return non-expired items since expired fields are already removed by Redis.
Applied to files:
internal/sms-gateway/online/service.gopkg/cache/memory.go
📚 Learning: 2025-09-10T23:45:45.502Z
Learnt from: capcom6
Repo: android-sms-gateway/server PR: 178
File: internal/sms-gateway/online/module.go:14-16
Timestamp: 2025-09-10T23:45:45.502Z
Learning: fx.Decorate in uber-go/fx creates module-scoped decorated dependencies, not global modifications. Using fx.Decorate(func(log *zap.Logger) *zap.Logger { return log.Named("module_name") }) is a correct pattern for creating module-specific loggers that are scoped only to that module's dependency graph.
Applied to files:
internal/sms-gateway/modules/settings/module.gointernal/sms-gateway/modules/events/module.gointernal/worker/app.go
📚 Learning: 2025-07-22T00:31:28.796Z
Learnt from: capcom6
Repo: android-sms-gateway/server PR: 154
File: internal/sms-gateway/modules/sse/service.go:67-79
Timestamp: 2025-07-22T00:31:28.796Z
Learning: In the SSE service Send method (internal/sms-gateway/modules/sse/service.go), blocking on full channels is intentional and acceptable. Timeout control is handled at the calling code level through context, not within the Send method itself. The user prefers this approach over adding a default case that would drop messages when channels are full.
Applied to files:
internal/sms-gateway/modules/sse/service.gointernal/sms-gateway/modules/messages/service.go
📚 Learning: 2025-07-22T00:34:31.896Z
Learnt from: capcom6
Repo: android-sms-gateway/server PR: 154
File: internal/sms-gateway/modules/sse/service.go:89-92
Timestamp: 2025-07-22T00:34:31.896Z
Learning: In Go SSE implementations, when cleaning up connections with buffered channels, avoid closing the data channels directly as this can cause panics if concurrent goroutines attempt to write to them. Instead, use a separate close signal channel to notify writers that the connection should be terminated, allowing the data channels to be garbage collected naturally when the connection is removed.
Applied to files:
internal/sms-gateway/modules/sse/service.gointernal/sms-gateway/modules/sse/errors.go
🧬 Code graph analysis (40)
internal/config/module.go (6)
internal/sms-gateway/app.go (1)
Module(33-58)internal/sms-gateway/modules/sse/config.go (4)
Option(5-5)Config(7-9)NewConfig(19-27)WithKeepAlivePeriod(33-41)internal/config/config.go (14)
Config(10-20)Default(89-127)HTTP(27-33)Database(44-55)Gateway(22-25)GatewayModePrivate(7-7)API(35-38)GatewayModePublic(6-6)OpenAPI(40-42)Messages(75-78)Tasks(63-65)SSE(71-73)Cache(80-82)PubSub(84-87)internal/sms-gateway/modules/push/service.go (1)
Config(23-30)internal/sms-gateway/pubsub/config.go (1)
Config(4-7)internal/sms-gateway/handlers/config.go (1)
Config(3-11)
internal/sms-gateway/modules/settings/repository.go (1)
internal/sms-gateway/modules/settings/models.go (1)
DeviceSettings(10-17)
internal/sms-gateway/pubsub/pubsub.go (4)
internal/sms-gateway/pubsub/config.go (1)
Config(4-7)pkg/pubsub/options.go (1)
WithBufferSize(15-19)pkg/pubsub/memory.go (1)
NewMemory(24-38)pkg/pubsub/redis.go (2)
NewRedis(42-78)RedisConfig(15-27)
internal/sms-gateway/handlers/messages/mobile.go (5)
internal/sms-gateway/handlers/events/mobile.go (2)
NewMobileController(19-27)MobileController(13-17)internal/sms-gateway/handlers/settings/mobile.go (2)
NewMobileController(21-35)MobileController(14-19)internal/sms-gateway/handlers/webhooks/mobile.go (2)
NewMobileController(21-33)MobileController(15-19)internal/sms-gateway/handlers/base/handler.go (1)
Handler(15-18)internal/sms-gateway/models/models.go (1)
Device(34-45)
internal/sms-gateway/modules/settings/models.go (2)
internal/sms-gateway/models/models.go (2)
TimedModel(7-10)User(18-24)internal/sms-gateway/models/migration.go (1)
Migrate(13-18)
internal/sms-gateway/handlers/messages/3rdparty.go (6)
internal/sms-gateway/handlers/base/handler.go (1)
Handler(15-18)internal/sms-gateway/modules/messages/service.go (1)
EnqueueOptions(22-24)internal/sms-gateway/modules/messages/errors.go (2)
ValidationError(9-9)ErrNoContent(6-6)internal/sms-gateway/modules/messages/repository.go (3)
ErrMultipleMessagesFound(18-18)ErrMessageNotFound(16-16)ErrMessageAlreadyExists(17-17)internal/sms-gateway/modules/devices/repository.go (3)
ErrNotFound(14-14)ErrInvalidFilter(15-15)ErrMoreThanOne(16-16)internal/sms-gateway/modules/devices/errors.go (1)
ErrInvalidUser(6-6)
internal/sms-gateway/handlers/webhooks/3rdparty.go (5)
internal/sms-gateway/handlers/messages/3rdparty.go (2)
NewThirdPartyController(44-53)ThirdPartyController(37-42)internal/sms-gateway/handlers/base/handler.go (1)
Handler(15-18)internal/sms-gateway/modules/webhooks/models.go (1)
Webhook(11-25)internal/sms-gateway/modules/webhooks/errors.go (1)
IsValidationError(34-36)internal/sms-gateway/modules/webhooks/repository_filter.go (1)
WithExtID(7-11)
internal/sms-gateway/modules/settings/module.go (1)
internal/sms-gateway/app.go (1)
Module(33-58)
internal/sms-gateway/modules/auth/repository.go (1)
internal/sms-gateway/models/models.go (1)
User(18-24)
internal/sms-gateway/app.go (4)
internal/config/module.go (1)
Module(23-140)internal/sms-gateway/handlers/module.go (1)
Module(15-41)internal/sms-gateway/modules/db/module.go (1)
Module(16-26)internal/worker/app.go (1)
Run(19-28)
internal/sms-gateway/modules/push/client/types.go (1)
internal/sms-gateway/modules/messages/models.go (1)
Message(36-56)
internal/sms-gateway/modules/sse/service.go (2)
internal/sms-gateway/modules/sse/errors.go (1)
ErrNoConnection(6-6)internal/sms-gateway/modules/sse/metrics.go (2)
ErrorTypeMarshalError(22-22)ErrorTypeWriteFailure(21-21)
internal/sms-gateway/modules/db/health.go (1)
pkg/health/types.go (2)
Checks(67-67)Provider(69-75)
internal/sms-gateway/modules/messages/errors.go (1)
internal/sms-gateway/modules/webhooks/errors.go (1)
ValidationError(12-16)
internal/sms-gateway/modules/push/module.go (4)
internal/sms-gateway/modules/push/fcm/client.go (2)
New(21-27)Client(14-19)internal/sms-gateway/modules/push/service.go (1)
New(43-73)internal/sms-gateway/modules/push/upstream/client.go (2)
New(29-35)Client(22-27)internal/sms-gateway/modules/push/client/types.go (1)
Client(9-13)
internal/sms-gateway/modules/devices/repository.go (2)
internal/sms-gateway/handlers/middlewares/deviceauth/deviceauth.go (1)
New(20-44)internal/sms-gateway/models/models.go (1)
Device(34-45)
internal/sms-gateway/modules/push/types.go (3)
internal/sms-gateway/modules/events/types.go (1)
Event(10-13)internal/sms-gateway/modules/push/client/types.go (1)
Event(20-23)internal/sms-gateway/modules/sse/types.go (1)
Event(7-10)
internal/sms-gateway/modules/messages/cache.go (2)
internal/sms-gateway/modules/messages/domain.go (1)
MessageStateOut(38-44)pkg/cache/options.go (2)
WithTTL(20-31)AndSetTTL(65-69)
internal/sms-gateway/modules/push/service.go (2)
internal/sms-gateway/modules/push/client/types.go (3)
Client(9-13)Event(20-23)Message(15-18)internal/sms-gateway/modules/push/types.go (1)
Event(17-17)
internal/sms-gateway/handlers/devices/3rdparty.go (2)
internal/sms-gateway/handlers/base/handler.go (1)
Handler(15-18)internal/sms-gateway/handlers/logs/3rdparty.go (2)
NewThirdPartyController(24-31)ThirdPartyController(20-22)
internal/sms-gateway/modules/settings/utils.go (1)
internal/sms-gateway/pubsub/pubsub.go (1)
New(19-51)
internal/sms-gateway/modules/push/upstream/client.go (2)
internal/sms-gateway/modules/push/fcm/client.go (2)
New(21-27)Client(14-19)internal/sms-gateway/modules/push/client/types.go (2)
Client(9-13)Message(15-18)
internal/sms-gateway/modules/events/module.go (2)
internal/sms-gateway/app.go (1)
Module(33-58)internal/sms-gateway/modules/events/service.go (2)
NewService(33-52)Service(20-31)
internal/sms-gateway/modules/push/fcm/client.go (4)
internal/sms-gateway/modules/push/fcm/errors.go (1)
ErrInitializationFailed(6-6)internal/sms-gateway/modules/push/client/types.go (3)
Client(9-13)Message(15-18)Event(20-23)internal/sms-gateway/modules/push/upstream/client.go (1)
Client(22-27)internal/sms-gateway/modules/push/types.go (1)
Event(17-17)
internal/config/config.go (4)
internal/worker/config/config.go (3)
Default(34-66)Config(9-13)Tasks(15-19)internal/sms-gateway/modules/sse/config.go (1)
Config(7-9)internal/sms-gateway/modules/messages/config.go (1)
Config(5-8)internal/sms-gateway/pubsub/config.go (1)
Config(4-7)
internal/sms-gateway/modules/devices/service.go (3)
internal/sms-gateway/models/models.go (1)
Device(34-45)internal/sms-gateway/modules/devices/repository_filter.go (4)
SelectFilter(9-9)WithUserID(23-27)WithID(11-15)ActiveWithin(29-33)internal/sms-gateway/modules/devices/repository.go (1)
ErrNotFound(14-14)
internal/sms-gateway/handlers/mobile.go (1)
internal/sms-gateway/models/models.go (2)
Device(34-45)User(18-24)
internal/sms-gateway/modules/messages/service.go (6)
internal/sms-gateway/modules/messages/repository_filter.go (3)
Order(7-7)SelectFilter(16-23)SelectOptions(51-62)internal/sms-gateway/modules/messages/domain.go (4)
MessageOut(25-29)MessageStateIn(31-36)MessageStateOut(38-44)MessageIn(9-23)internal/sms-gateway/modules/messages/models.go (6)
MessageState(157-162)ProcessingState(13-13)ProcessingStatePending(17-17)MessageRecipient(139-145)Message(36-56)NewMessage(58-83)internal/sms-gateway/modules/messages/repository.go (1)
ErrMessageNotFound(16-16)internal/sms-gateway/modules/events/events.go (1)
NewMessageEnqueuedEvent(9-11)internal/sms-gateway/modules/messages/errors.go (2)
ErrNoContent(6-6)ValidationError(9-9)
internal/sms-gateway/modules/webhooks/service.go (1)
internal/sms-gateway/modules/webhooks/errors.go (1)
ErrInvalidEvent(9-9)
internal/sms-gateway/handlers/settings/mobile.go (6)
internal/sms-gateway/handlers/base/handler.go (1)
Handler(15-18)internal/sms-gateway/modules/settings/service.go (1)
Service(19-25)internal/sms-gateway/handlers/events/mobile.go (2)
NewMobileController(19-27)MobileController(13-17)internal/sms-gateway/handlers/messages/mobile.go (2)
NewMobileController(35-43)MobileController(29-33)internal/sms-gateway/handlers/webhooks/mobile.go (2)
NewMobileController(21-33)MobileController(15-19)internal/sms-gateway/models/models.go (1)
Device(34-45)
internal/sms-gateway/modules/messages/repository.go (2)
internal/sms-gateway/modules/messages/repository_filter.go (3)
SelectFilter(16-23)SelectOptions(51-62)Order(7-7)internal/sms-gateway/modules/messages/models.go (2)
Message(36-56)MessageRecipient(139-145)
internal/sms-gateway/modules/auth/service.go (6)
internal/sms-gateway/online/service.go (2)
New(28-37)Service(14-17)internal/sms-gateway/handlers/middlewares/deviceauth/deviceauth.go (1)
New(20-44)internal/sms-gateway/modules/auth/types.go (1)
OneTimeCode(15-18)internal/sms-gateway/models/models.go (4)
User(18-24)NewUser(26-32)Device(34-45)NewDevice(47-53)pkg/crypto/passwords.go (2)
MakeBCryptHash(14-20)CompareBCryptHash(22-27)internal/sms-gateway/modules/auth/errors.go (1)
ErrAuthorizationFailed(6-6)
internal/sms-gateway/modules/messages/converters.go (1)
internal/sms-gateway/modules/messages/domain.go (1)
MessageOut(25-29)
internal/sms-gateway/modules/messages/models.go (1)
internal/sms-gateway/models/models.go (1)
SoftDeletableModel(12-16)
pkg/cache/memory.go (2)
pkg/cache/options.go (3)
GetOption(49-49)AndDelete(89-93)Option(6-6)pkg/cache/cache.go (1)
Cache(5-40)
internal/sms-gateway/handlers/webhooks/mobile.go (7)
internal/sms-gateway/handlers/base/handler.go (1)
Handler(15-18)internal/sms-gateway/modules/webhooks/service.go (1)
Service(28-37)internal/sms-gateway/handlers/events/mobile.go (2)
NewMobileController(19-27)MobileController(13-17)internal/sms-gateway/handlers/messages/mobile.go (2)
NewMobileController(35-43)MobileController(29-33)internal/sms-gateway/handlers/settings/mobile.go (2)
NewMobileController(21-35)MobileController(14-19)internal/sms-gateway/models/models.go (1)
Device(34-45)internal/sms-gateway/modules/webhooks/repository_filter.go (1)
WithDeviceID(41-46)
internal/sms-gateway/handlers/middlewares/userauth/userauth.go (1)
internal/sms-gateway/models/models.go (1)
User(18-24)
internal/worker/executor/service.go (1)
internal/worker/config/types.go (1)
Duration(11-11)
internal/sms-gateway/modules/events/service.go (8)
internal/config/config.go (1)
PubSub(84-87)internal/sms-gateway/pubsub/pubsub.go (1)
PubSub(15-15)internal/sms-gateway/modules/events/errors.go (1)
ErrValidationFailed(6-6)internal/sms-gateway/modules/events/metrics.go (6)
DeliveryTypeUnknown(20-20)FailureReasonPublishError(23-23)EventTypeUnknown(26-26)FailureReasonSerializationError(22-22)DeliveryTypePush(18-18)FailureReasonProviderFailed(24-24)internal/sms-gateway/modules/events/types.go (1)
Event(10-13)internal/sms-gateway/modules/push/client/types.go (1)
Event(20-23)internal/sms-gateway/modules/push/types.go (1)
Event(17-17)internal/sms-gateway/modules/sse/types.go (1)
Event(7-10)
internal/sms-gateway/handlers/messages/params.go (1)
internal/sms-gateway/modules/messages/repository_filter.go (3)
SelectFilter(16-23)SelectOptions(51-62)Order(7-7)
🪛 checkmake (0.2.2)
Makefile
[warning] 10-10: Missing required phony target "all"
(minphony)
[warning] 10-10: Missing required phony target "clean"
(minphony)
[warning] 10-10: Missing required phony target "test"
(minphony)
[warning] 15-15: Target "all" should be declared PHONY.
(phonydeclared)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (5)
- GitHub Check: E2E
- GitHub Check: Benchmark
- GitHub Check: Test
- GitHub Check: Lint
- GitHub Check: Analyze (go)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 0
🧹 Nitpick comments (1)
internal/sms-gateway/modules/sse/service.go (1)
46-46: Optional: Explicit mutex initialization is redundant.The zero value of
sync.RWMutexis ready to use in Go. The explicit initializationsync.RWMutex{}is equivalent to omitting the field from the struct literal entirely. This is a minor style consideration and doesn't affect functionality.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (2)
internal/sms-gateway/modules/sse/service.go(8 hunks)internal/worker/executor/service.go(3 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
- internal/worker/executor/service.go
🧰 Additional context used
🧠 Learnings (2)
📚 Learning: 2025-07-22T00:31:28.796Z
Learnt from: capcom6
Repo: android-sms-gateway/server PR: 154
File: internal/sms-gateway/modules/sse/service.go:67-79
Timestamp: 2025-07-22T00:31:28.796Z
Learning: In the SSE service Send method (internal/sms-gateway/modules/sse/service.go), blocking on full channels is intentional and acceptable. Timeout control is handled at the calling code level through context, not within the Send method itself. The user prefers this approach over adding a default case that would drop messages when channels are full.
Applied to files:
internal/sms-gateway/modules/sse/service.go
📚 Learning: 2025-07-22T00:34:31.896Z
Learnt from: capcom6
Repo: android-sms-gateway/server PR: 154
File: internal/sms-gateway/modules/sse/service.go:89-92
Timestamp: 2025-07-22T00:34:31.896Z
Learning: In Go SSE implementations, when cleaning up connections with buffered channels, avoid closing the data channels directly as this can cause panics if concurrent goroutines attempt to write to them. Instead, use a separate close signal channel to notify writers that the connection should be terminated, allowing the data channels to be garbage collected naturally when the connection is removed.
Applied to files:
internal/sms-gateway/modules/sse/service.go
🧬 Code graph analysis (1)
internal/sms-gateway/modules/sse/service.go (2)
internal/sms-gateway/modules/sse/errors.go (1)
ErrNoConnection(6-6)internal/sms-gateway/modules/sse/metrics.go (2)
ErrorTypeMarshalError(22-22)ErrorTypeWriteFailure(21-21)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
- GitHub Check: Test
- GitHub Check: Benchmark
- GitHub Check: Analyze (go)
🔇 Additional comments (3)
internal/sms-gateway/modules/sse/service.go (3)
17-19: LGTM! Good refactoring to extract the buffer size.Introducing the
eventsBufferSizeconstant improves maintainability and makes the buffer size configurable from a single location.
150-163: LGTM! Write error handling correctly exits the stream loop.The implementation now properly handles write failures by capturing the error status outside the observer closure and returning from
handleStreamwhen a write fails. This ensures the deferredremoveConnectionruns and prevents the connection from remaining in a zombie state. This correctly addresses the previous review concern.
120-179: LGTM! Excellent refactoring to extract connection handling.The extraction of per-connection logic into
handleStreamimproves modularity, testability, and readability. The method properly encapsulates:
- Connection registration and cleanup via deferred
removeConnection- Conditional ticker-based keep-alive mechanism
- Event streaming with proper error handling
- Graceful shutdown on close signal
The separation of concerns between
Handler(response setup) andhandleStream(connection lifecycle) is well-designed.
|
Worker deployed... |
|
Deployed... |
Summary by CodeRabbit
New Features
Bug Fixes
Chores
✏️ Tip: You can customize this high-level summary in your review settings.