-
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
Conversation
|
Warning Rate limit exceeded@capcom6 has exceeded the limit for the number of commits or files that can be reviewed per hour. Please wait 12 minutes and 19 seconds before requesting another review. ⌛ How to resolve this issue?After the wait time has elapsed, a review can be triggered using the We recommend that you space out your commits to avoid hitting the rate limit. 🚦 How do rate limits work?CodeRabbit enforces hourly rate limits for each developer per organization. Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout. Please see our FAQ for further information. ⛔ Files ignored due to path filters (1)
📒 Files selected for processing (92)
WalkthroughRemoved legacy Docker build/publish workflows and added Goreleaser-driven CI; introduced many API and implementation refactors (Module vars → Module() functions), push client abstraction, exported cache/pubsub types, messages/auth signature changes, new constructors and sentinel errors, model/migration updates, and numerous lint/style adjustments. Changes
Sequence Diagram(s)sequenceDiagram
actor Dev as Developer
participant CI as CI (Goreleaser)
participant FX as FX App
participant ModuleFactory as Module()
participant newClient as newClient()
participant Client as client.Client
participant PushSvc as Push Service
participant Messages as Messages Service
Note right of CI `#FFF3DE`: CI/release now uses Goreleaser
Dev->>CI: push tag / open PR
CI-->>Dev: build/publish artifacts
Note right of ModuleFactory `#DFF1DF`: Module() factories replace package vars
FX->>ModuleFactory: invoke Module()
ModuleFactory-->>FX: provide configs, services, client factory
Note right of newClient `#E8F4FF`: push client factory returns concrete client
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)
Messages->>PushSvc: Enqueue(...)
PushSvc->>Client: Send(ctx, []client.Message)
Client-->>PushSvc: results/errors
FX->>Client: Close(ctx) on stop
Estimated code review effort🎯 5 (Critical) | ⏱️ ~120+ minutes Focus review areas:
Possibly related PRs
Suggested labels
Pre-merge checks and finishing touches❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
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
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... |
fda176d to
8bbe45b
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
♻️ Duplicate comments (8)
internal/sms-gateway/modules/push/upstream/client.go (1)
62-62: Remove the unnecessary type conversion.This was already flagged in a previous review.
UpstreamPushRequestis a type alias for[]smsgateway.PushNotification, so the explicit conversion is redundant.internal/sms-gateway/handlers/mobile.go (1)
275-276: Handle or document the ignored error fromnanoid.Standard.The error from
nanoid.Standard(idGenSize)is still discarded. While failures are unlikely with a constant size of 21, silently ignoring initialization errors can make debugging painful if something does go wrong. Either handle it or explain why it’s safe to ignore:- 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:
const idGenSize = 21 - idGen, _ := nanoid.Standard(idGenSize) + // nanoid.Standard with size 21 is guaranteed not to error. + idGen, _ := nanoid.Standard(idGenSize)internal/sms-gateway/handlers/messages/params.go (1)
24-27: Fix date-range validation to compare parsed timestamps, not strings.
p.StartDate > p.EndDateis a lexicographical comparison, so valid RFC3339 timestamps with different offsets can be rejected (e.g."2025-11-10T12:00:00+02:00"vs"2025-11-10T11:00:00+00:00"). Parse both values totime.Timeand 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 - } - - return nil -} +func (p *thirdPartyGetQueryParams) Validate() error { + 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/errors.go (1)
3-13: Consider renamingValidationErrorto avoid cross‑package ambiguity.There is also a
webhooks.ValidationErrortype (struct) ininternal/sms-gateway/modules/webhooks/errors.go. Having two exportedValidationErrortypes in different packages is legal but can be confusing where both packages are imported together. A more specific name likeMessageValidationErrorwould make call sites clearer, but this is purely a naming/ergonomics concern, not a functional bug.internal/config/module.go (2)
88-109: Fix path defaulting logic to checkPathinstead ofHost.The condition on line 90 checks
cfg.HTTP.API.Host == ""but sets thePath. This means if a user explicitly sets a customPathbut leavesHostempty, theirPathwill be overwritten with/api. The check should verify ifPathis empty.Apply this diff:
- if cfg.HTTP.API.Host == "" { + if cfg.HTTP.API.Path == "" { cfg.HTTP.API.Path = "/api" }
26-37:fx.Privatemust be wrapped withfx.Annotate.Passing
fx.Privateas a separate argument tofx.Provideis incorrect—Fx will treat it as a constructor and fail at startup. You need to wrap the provider function withfx.Annotate.Apply this diff:
fx.Provide( - 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.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, + ), ),internal/sms-gateway/modules/messages/service.go (2)
176-218: Enqueue: initial state + cache are correct; reconsider returning non‑nil state on insert error.
prepareMessage, caching undermsg.ExtID(fixing the earlier cache‑key issue), and firingeventsSvc.Notifyin a goroutine all look good and align with the existing async notification pattern (based on learnings). The only oddity is returning a non‑nilstatewhenInsertfails:if insErr := s.messages.Insert(msg); insErr != nil { return state, insErr }This diverges from typical Go
(nil, err)conventions and makes it easier for future callers to misuse the partialstateif they ever forget to checkerrfirst or are unaware of this contract.If you don’t rely on the partial state on failure, I’d simplify to:
- if insErr := s.messages.Insert(msg); insErr != nil { - return state, insErr - } + if insErr := s.messages.Insert(msg); insErr != nil { + return nil, insErr + }Otherwise, consider adding a brief godoc comment for
Enqueuethat explicitly documents that a partially populatedstatemay be returned alongside a non‑nil error.
220-272: prepareMessage: content handling is solid; TTLgosecsuppression still needs an explicit justification or bound.The per‑row phone validation (with row index in the error),
NewMessageconstruction, and content switch (TextContentvsDataContent) withErrNoContentas a sentinel all look good and address the earlier dynamic‑error lint.The remaining concern is the
TTLhandling:if message.TTL != nil && *message.TTL > 0 { //nolint:gosec // not a problem validUntil = anys.AsPointer( time.Now().Add(time.Duration(*message.TTL) * time.Second), ) }If
TTLis user‑controlled and not type‑ or range‑constrained elsewhere, this unchecked conversion is exactly whatgosecwarns about (potentialtime.Durationoverflow on very large TTLs). At minimum, either:
- Add an explicit upper bound on TTL here, or
- Expand the comment to point to the upstream validation or type constraints that make the suppression safe (e.g., if
TTLis a smaller integer type whose max cannot overflowtime.Duration).For example, one possible pattern (tune the constant and error type as appropriate):
if message.TTL != nil && *message.TTL > 0 { - //nolint:gosec // not a problem - validUntil = anys.AsPointer( - time.Now().Add(time.Duration(*message.TTL) * time.Second), - ) + const maxTTLSeconds = 315360000 // example: 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), + ) }If you already have stronger guarantees on TTL elsewhere, surfacing that context in the comment would help future readers understand why the suppression is safe.
🧹 Nitpick comments (15)
internal/sms-gateway/modules/db/health.go (1)
20-21: Optional: Remove redundant explicit initialization.The explicit initialization
failedPings: atomic.Int64{}is redundant sinceatomic.Int64already has a zero value. You can safely omit this field from the struct literal.Apply this diff to simplify:
func newHealth(db *sql.DB) *health { return &health{ db: db, - - failedPings: atomic.Int64{}, } }internal/sms-gateway/modules/push/upstream/client.go (1)
18-20: Hardcoded API base URL.The
baseURLis hardcoded. Consider making this configurable via theoptionsmap to support different environments (staging, testing, self-hosted instances).internal/sms-gateway/pubsub/module.go (1)
20-22: EmptyOnStarthook is unnecessary.The
fx.Hookstruct doesn't require bothOnStartandOnStopto be defined. This no-op hook can be removed to reduce noise.lc.Append(fx.Hook{ - OnStart: func(_ context.Context) error { - return nil - }, OnStop: func(_ context.Context) error {internal/sms-gateway/modules/settings/models.go (1)
11-17: DeviceSettings embedding and constructor look good; consider minor optional tweaks
- Embedding
models.TimedModeland switchingMigrateto usenew(DeviceSettings)are consistent with the rest of the codebase and GORM usage.- The
NewDeviceSettingsconstructor is a nice centralization point; if you ever want to guaranteeSettingsis nevernilon write paths, you could normalize it there (e.g., treatnilas an empty map) and remove a few downstream nil-guards.- If you run linters that enforce GoDoc on exported symbols, consider adding a short comment for
NewDeviceSettingsto avoid warnings.Also applies to: 19-24, 28-31
internal/sms-gateway/modules/settings/repository.go (1)
16-23: GetSettings UserID initialization is a defensive improvement, not a required fixThe concern about empty
UserIDon not-found reads is well-intentioned, but verification shows this is not a practical issue:GetSettingsresults are not reused for writes. The service layer extracts only theSettingsmap and discards the struct. When updates occur,UpdateSettingscreates a freshDeviceSettingsviaNewDeviceSettings(userID, ...), so the emptyUserIDfrom a read-miss never reaches write operations.That said, the suggestion to initialize
UserIDupfront is still worthwhile—it clarifies intent and makes the struct state predictable should the code evolve. The proposed change is defensible as a refactor.internal/worker/locker/mysql.go (1)
29-29: Optional: Redundant explicit initialization.The explicit
sync.Mutex{}initialization is unnecessary since the zero value ofsync.Mutexis ready to use. You can simplify to just declaring the field without explicit initialization in the struct literal.- mu: sync.Mutex{}, + mu: sync.Mutex{}, // zero value is usable; explicit init optionalOr remove it from the literal entirely (it will be zero-initialized automatically):
- mu: sync.Mutex{}, conns: make(map[string]*sql.Conn),internal/sms-gateway/handlers/settings/3rdparty.go (1)
85-85: Optional: Consider retaining capacity hint.The previous
make(map[string]any, 8)capacity hint could reduce allocations if settings typically contain around 8 keys. For non-critical paths this is fine, but if you know the typical size, a capacity hint can be a small optimization.Also applies to: 119-119
internal/sms-gateway/handlers/messages/params.go (1)
58-76: Ensureminusage matches your Go/toolchain version.
ToOptionsusesmin(p.Limit, maxLimit). If you’re not on Go 1.21+ (whereminis a language builtin), this will not compile unless there’s a localminhelper in this package. If the project targets older Go, add a small helper instead:+func min(a, b int) int { + if a < b { + return a + } + return b +}Also applies to: 79-87
internal/sms-gateway/handlers/mobile.go (1)
95-136: Consider usingfiber.NewErrorfor internal failures inpostDevice.For
RegisterUser/RegisterDevicefailures you currentlyreturn fmt.Errorf("failed to …: %w", err), while other handlers in this file wrap errors asfiber.NewErrorwith explicit status codes. For consistency and clearer HTTP semantics, consider:- user, err = h.authSvc.RegisterUser(login, password) - if err != nil { - return fmt.Errorf("failed to create user: %w", err) - } + user, err = h.authSvc.RegisterUser(login, password) + if err != nil { + return fiber.NewError(fiber.StatusInternalServerError, "failed to create user") + } … - device, err := h.authSvc.RegisterDevice(user, req.Name, req.PushToken) - if err != nil { - return fmt.Errorf("failed to register device: %w", err) - } + device, err := h.authSvc.RegisterDevice(user, req.Name, req.PushToken) + if err != nil { + return fiber.NewError(fiber.StatusInternalServerError, "failed to register device") + }This keeps client-visible behavior aligned with
patchDevice,getUserCode, andchangePassword.internal/sms-gateway/modules/devices/service.go (1)
7-7: GetAny logic looks correct; consider API consistency and Go version formath/rand/v2.
GetAnybuilds filters correctly (user ID, optional device ID, optionalActiveWithin) and handles 0 / 1 / many results sensibly. Therand.IntN(len(devices))call is safe because it only runs whenlen(devices) > 1.- This method returns
*models.DevicewhileGetreturnsmodels.Deviceby value. If you don’t need in‑place mutation by callers, you might consider returning a value here too for API consistency across the service.- Importing
math/rand/v2is fine for non‑critical randomness, but it does assume a recent enough Go toolchain; please double‑check that yourgoversion ingo.mod(and CI) supports this package.The logging and temporary‑error variable refactors (
setErr,cacheErr,rmErr) are clear and non‑disruptive.Also applies to: 80-107, 121-123, 131-134, 156-160, 180-184, 188-190
internal/config/module.go (1)
118-122: TODO: MakeUnusedLifetimeconfigurable.There's a TODO to make the
UnusedLifetimevalue configurable instead of hardcoding it to 365 days.Would you like me to open an issue to track this task, or help generate a configuration option for this value?
internal/config/config.go (1)
63-69: Consider adding deprecation notice to the environment variable documentation.The
Tasks.Hashingsection is marked as deprecated in the struct tag comment. Ensure this deprecation is also documented in any external configuration documentation or README to help users migrate to the newMessages.HashingIntervalSecondsfield.internal/sms-gateway/modules/messages/repository.go (2)
20-24: Consider removing theemptyfield.The
empty Messagefield is only used as a placeholder forDeleteoperations. Using(*Message)(nil)or&Message{}directly in theCleanupmethod would be more idiomatic and avoid storing unnecessary state in the repository struct.type Repository struct { db *gorm.DB - - empty Message }And in
Cleanup:- Delete(&r.empty) + Delete((*Message)(nil))
105-112: Builder pattern usage is unconventional.The
*new(SelectFilter).WithDeviceID(...).WithState(...)pattern works but is unusual. If theWith*methods return pointer receivers, consider starting with a zero value directly:func (r *Repository) SelectPending(deviceID string, order Order) ([]Message, error) { + filter := SelectFilter{} + options := SelectOptions{} messages, _, err := r.Select( - *new(SelectFilter).WithDeviceID(deviceID).WithState(ProcessingStatePending), - *new(SelectOptions).IncludeRecipients().WithLimit(maxPendingBatch).WithOrderBy(order), + *filter.WithDeviceID(deviceID).WithState(ProcessingStatePending), + *options.IncludeRecipients().WithLimit(maxPendingBatch).WithOrderBy(order), ) return messages, err }Alternatively, if methods return values (not pointers), use the struct literal directly without dereferencing.
internal/sms-gateway/modules/messages/service.go (1)
341-357: cleanPhoneNumber: usingValidationErrorimproves error signalling; region could be configurable later.Switching to
ValidationErrorfor parse/validity/type failures while still returning E.164‑formatted numbers on success should make it easier for callers to distinguish user input problems from other error types. If you ever need multi‑region support, consider making the"RU"region configurable (e.g., viaConfig) instead of hard‑coded here.
📜 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 (91)
api/requests.http(0 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/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)test/e2e/mobile_test.go(1 hunks)test/e2e/utils_test.go(1 hunks)
💤 Files with no reviewable changes (3)
- api/requests.http
- go.mod
- internal/sms-gateway/modules/messages/service_test.go
🚧 Files skipped from review as they are similar to previous changes (32)
- internal/sms-gateway/online/metrics.go
- internal/sms-gateway/modules/webhooks/repository_filter.go
- internal/sms-gateway/modules/messages/repository_filter.go
- internal/sms-gateway/modules/webhooks/module.go
- internal/sms-gateway/modules/db/module.go
- internal/sms-gateway/handlers/module.go
- internal/sms-gateway/modules/messages/workers.go
- internal/worker/config/config.go
- internal/sms-gateway/modules/webhooks/errors.go
- internal/sms-gateway/modules/events/module.go
- internal/sms-gateway/handlers/upstream.go
- internal/sms-gateway/handlers/messages/mobile.go
- internal/sms-gateway/models/module.go
- internal/sms-gateway/handlers/base/handler.go
- internal/sms-gateway/cache/errors.go
- internal/worker/app.go
- internal/sms-gateway/online/service.go
- internal/sms-gateway/handlers/logs/3rdparty.go
- internal/version/version.go
- test/e2e/mobile_test.go
- internal/sms-gateway/modules/devices/repository_filter.go
- internal/sms-gateway/modules/push/module.go
- internal/sms-gateway/models/models.go
- cmd/sms-gateway/main.go
- internal/sms-gateway/handlers/converters/messages.go
- internal/worker/executor/metrics.go
- internal/sms-gateway/handlers/messages/3rdparty.go
- internal/sms-gateway/modules/settings/module.go
- internal/worker/config/types.go
- internal/sms-gateway/modules/settings/service.go
- internal/sms-gateway/modules/messages/models.go
- internal/sms-gateway/handlers/root.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/config/module.gointernal/sms-gateway/pubsub/module.gointernal/sms-gateway/modules/metrics/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.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 (37)
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 (2)
internal/config/config.go (11)
Config(10-20)Default(89-127)HTTP(27-33)Database(44-55)Gateway(22-25)GatewayModePublic(6-6)Messages(75-78)Tasks(63-65)SSE(71-73)Cache(80-82)PubSub(84-87)internal/worker/config/config.go (3)
Config(9-13)Default(34-66)Tasks(15-19)
internal/sms-gateway/models/migration.go (1)
internal/sms-gateway/models/models.go (2)
User(18-24)Device(34-45)
internal/worker/locker/mysql.go (1)
internal/worker/locker/locker.go (1)
ErrLockNotAcquired(9-9)
internal/sms-gateway/app.go (2)
internal/config/module.go (1)
Module(23-140)internal/worker/app.go (1)
Run(19-28)
internal/sms-gateway/modules/sse/module.go (6)
internal/sms-gateway/app.go (1)
Module(33-58)internal/sms-gateway/modules/events/module.go (1)
Module(10-39)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/webhooks/module.go (1)
Module(9-20)
internal/worker/executor/service.go (1)
internal/worker/config/types.go (1)
Duration(11-11)
internal/sms-gateway/handlers/settings/mobile.go (4)
internal/sms-gateway/handlers/base/handler.go (1)
Handler(15-18)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/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/sse/config.go (3)
internal/config/config.go (1)
Config(10-20)internal/sms-gateway/modules/push/service.go (1)
Config(23-30)internal/worker/config/types.go (1)
Duration(11-11)
internal/sms-gateway/handlers/health.go (1)
pkg/health/types.go (1)
CheckDetail(54-64)
internal/sms-gateway/handlers/devices/3rdparty.go (4)
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/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)
internal/sms-gateway/handlers/webhooks/3rdparty.go (5)
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/base/handler.go (1)
Handler(15-18)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/messages/errors.go (1)
internal/sms-gateway/modules/webhooks/errors.go (1)
ValidationError(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/pubsub/pubsub.go (4)
internal/config/config.go (2)
Config(10-20)PubSub(84-87)internal/sms-gateway/pubsub/config.go (1)
Config(4-7)pkg/pubsub/options.go (1)
WithBufferSize(15-19)pkg/pubsub/redis.go (2)
NewRedis(42-78)RedisConfig(15-27)
internal/sms-gateway/cache/factory.go (3)
internal/sms-gateway/cache/errors.go (1)
ErrInvalidConfig(6-6)pkg/cache/errors.go (1)
ErrInvalidConfig(7-7)internal/config/config.go (1)
Cache(80-82)
internal/sms-gateway/modules/db/health.go (1)
pkg/health/types.go (2)
Checks(67-67)Provider(69-75)
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/handlers/mobile.go (1)
internal/sms-gateway/models/models.go (2)
Device(34-45)User(18-24)
internal/sms-gateway/modules/devices/repository.go (3)
internal/sms-gateway/models/models.go (1)
Device(34-45)internal/sms-gateway/modules/messages/repository.go (1)
Repository(20-24)internal/sms-gateway/modules/webhooks/repository.go (1)
Repository(8-12)
internal/sms-gateway/modules/settings/models.go (4)
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/modules/messages/models.go (1)
Migrate(164-169)internal/sms-gateway/modules/webhooks/models.go (1)
Migrate(38-43)
internal/sms-gateway/modules/webhooks/service.go (1)
internal/sms-gateway/modules/webhooks/errors.go (1)
ErrInvalidEvent(9-9)
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/webhooks/mobile.go (4)
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/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/middlewares/userauth/userauth.go (1)
internal/sms-gateway/models/models.go (1)
User(18-24)
internal/sms-gateway/modules/messages/repository.go (2)
internal/sms-gateway/modules/messages/models.go (2)
Message(36-56)MessageRecipient(139-145)internal/sms-gateway/modules/messages/repository_filter.go (3)
SelectFilter(16-23)SelectOptions(51-62)Order(7-7)
internal/sms-gateway/handlers/events/mobile.go (1)
internal/sms-gateway/models/models.go (1)
Device(34-45)
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/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/metrics/module.go (8)
internal/sms-gateway/app.go (1)
Module(33-58)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/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/modules/messages/converters.go (1)
internal/sms-gateway/modules/messages/domain.go (1)
MessageOut(25-29)
internal/sms-gateway/modules/settings/repository.go (1)
internal/sms-gateway/modules/settings/models.go (1)
DeviceSettings(10-17)
internal/sms-gateway/modules/webhooks/repository.go (3)
internal/sms-gateway/modules/webhooks/models.go (1)
Webhook(11-25)internal/sms-gateway/modules/devices/repository.go (2)
NewRepository(25-29)Repository(19-23)internal/sms-gateway/modules/messages/repository.go (2)
NewRepository(26-30)Repository(20-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/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)
🪛 GitHub Check: Lint
internal/sms-gateway/modules/webhooks/repository.go
[failure] 15-15:
webhooks.Repository is missing field empty (exhaustruct)
⏰ 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)
8bbe45b to
db4cd00
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 (2)
internal/sms-gateway/modules/sse/service.go (1)
54-105: Remove thedefaultcase from Send—event delivery should not drop messages; restore blocking backpressure.The current
Sendmethod (lines 74–92) includes adefaultcase that drops events whenconn.channelis full, contradicting prior learnings that blocking on full channels was intentional and preferred. This is a regression in delivery guarantees—consider removing thedefaultbranch so backpressure remains blocking rather than lossy.Fix ActiveConnections metric decrement in graceful shutdown.
Service.Close()(lines 107–118) closescloseSignaland deletes map entries directly without callingremoveConnection. SinceregisterConnectionincrementsActiveConnectionsand onlyremoveConnectiondecrements it, the metric will not be updated during graceful shutdown. Either loop through connections and callremoveConnectionfor each, or decrement the metric alongside the map cleanup inClose().internal/sms-gateway/handlers/settings/3rdparty.go (1)
56-64: Avoid surfacing wrapped internal errors to clients in 3rd‑party settings handlersIn
get,put, andpatchyou return wrapped errors directly:
- Line 60:
fmt.Errorf("failed to get settings: %w", err)- Line 94:
fmt.Errorf("failed to update settings: %w", err)- Line 127:
fmt.Errorf("failed to update settings: %w", err)Since this router does not use a custom
errorHandler, these errors will be rendered by Fiber’s global error handler, potentially exposing internal DB/config details (and any context the service attaches) to API consumers. In the mobile settings handler you already switched to logging and returning a generic 500, which is safer.Consider logging the detailed error and returning a generic 500 here as well, for example:
func (h *ThirdPartyController) get(user models.User, c *fiber.Ctx) error { settings, err := h.settingsSvc.GetSettings(user.ID, true) if err != nil { - return fmt.Errorf("failed to get settings: %w", err) + h.Logger.Error("failed to get settings", + zap.Error(err), + zap.String("user_id", user.ID), + ) + return fiber.NewError(fiber.StatusInternalServerError, "Failed to get settings") } @@ updated, err := h.settingsSvc.ReplaceSettings(user.ID, settings) if err != nil { - return fmt.Errorf("failed to update settings: %w", err) + h.Logger.Error("failed to replace settings", + zap.Error(err), + zap.String("user_id", user.ID), + ) + return fiber.NewError(fiber.StatusInternalServerError, "Failed to update settings") } @@ updated, err := h.settingsSvc.UpdateSettings(user.ID, settings) if err != nil { - return fmt.Errorf("failed to update settings: %w", err) + h.Logger.Error("failed to update settings", + zap.Error(err), + zap.String("user_id", user.ID), + ) + return fiber.NewError(fiber.StatusInternalServerError, "Failed to update settings") }This aligns with the mobile handler and avoids leaking internal errors to clients.
Also applies to: 79-98, 113-131
♻️ Duplicate comments (10)
internal/sms-gateway/handlers/upstream.go (1)
66-69: Validation error status code fix confirmed.The change from
StatusBadGatewaytoStatusBadRequestcorrectly returns HTTP 400 for client validation errors. This addresses the previously flagged issue.internal/sms-gateway/modules/push/upstream/client.go (1)
62-62: Remove the unnecessary type conversion.This was flagged in a previous review. Since
UpstreamPushRequestis a type alias for[]smsgateway.PushNotification, andpayloadis already that type, the explicit conversion is redundant.- payloadBytes, err := json.Marshal(smsgateway.UpstreamPushRequest(payload)) //nolint:unconvert //type checking + payloadBytes, err := json.Marshal(payload)internal/sms-gateway/modules/messages/errors.go (1)
9-13: Naming collision with webhooks.ValidationError remains unresolved.This is the same issue previously flagged:
messages.ValidationError(string type) collides withwebhooks.ValidationError(struct type), which will require package aliases if both packages are imported together.internal/version/version.go (1)
7-7: Fix the grammatical error in the comment.The comment still contains a grammatical error: "when build" should be "when building" or "during build". This was identified in a previous review but not fully addressed.
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"`.internal/sms-gateway/modules/settings/module.go (1)
9-23: fx.Private ordering still doesn’t make newRepository privateIn:
fx.Provide( newRepository, fx.Private, ),
fx.Privateonly applies to constructors that come after it in the samefx.Providecall, sonewRepositoryremains visible to the wider graph andfx.Privateis effectively a no‑op here. If you intendnewRepositoryto be module‑private, flip the order:fx.Provide( fx.Private, newRepository, )Otherwise, consider removing
fx.Privatealtogether to avoid confusion.In uber-go/fx, does `fx.Private` only apply to constructors that follow it in the same `fx.Provide`/`fx.Supply` call (i.e., `fx.Provide(fx.Private, c1)`), and not to constructors listed before it (e.g., `fx.Provide(c1, fx.Private)`?internal/config/module.go (1)
22-37: Revisit HTTP API Path defaulting condition (Host vs Path emptiness).In the handlers.Config provider,
cfg.HTTP.API.Pathis defaulted to"/api"whencfg.HTTP.API.Host == "". This will overwrite any explicitPatha user configured while intentionally leavingHostempty (e.g., to derive host from the request), which was the concern in the earlier review.To avoid clobbering user‑supplied paths, consider changing the condition to default only when
Pathitself is empty:- // Default and normalize API path/host - if cfg.HTTP.API.Host == "" { - cfg.HTTP.API.Path = "/api" - } + // Default and normalize API path/host + if cfg.HTTP.API.Path == "" { + cfg.HTTP.API.Path = "/api" + }The subsequent normalization and host‑scheme stripping can stay as‑is.
Also applies to: 88-108
internal/sms-gateway/handlers/messages/3rdparty.go (2)
189-200: Standardize capitalization of user-facing 5xx error messagesThe 500 responses here still start with lowercase “failed”:
- Line 199:
"failed to retrieve message history"- Line 231:
"failed to get message state"- Line 265:
"failed to get device"- Line 269:
"failed to export inbox"- Line 312:
"failed to handle request"Earlier comments on this file suggested capitalizing these for consistency (“Failed …”). Not required functionally, but worth aligning if you’re standardizing error text.
- 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") - 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") - return fiber.NewError(fiber.StatusInternalServerError, "failed to handle request") + return fiber.NewError(fiber.StatusInternalServerError, "Failed to handle request")Also applies to: 230-231, 263-269, 311-312
275-313: Centralized errorHandler nicely maps domain errors to HTTP codesThe middleware cleanly:
- lets existing
*fiber.Errorpass through- maps
messages.ValidationErrorand other messages/devices sentinels to 400/404/409- falls back to a generic 500 with logging.
The switch on value-typed
messages.ValidationErrorfixes the earlier pointer-type matching issue. This should give consistent HTTP responses across handlers.internal/sms-gateway/handlers/messages/params.go (1)
24-27: Fix date-range validation to compare parsed timestamps, not strings
p.StartDate > p.EndDateis a string comparison, so valid ranges with different offsets can be incorrectly rejected (e.g.,"2025-11-10T12:00:00+02:00"vs"2025-11-10T11:00:00+00:00").Since the fields are already RFC3339-validated, parse both to
time.Timeand 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 + } + }This was flagged previously; the current version still uses string comparison.
internal/sms-gateway/modules/messages/service.go (1)
176-218: Address TTL overflow risk and normalize Enqueue's error contractBoth concerns identified are valid and should be addressed:
TTL → time.Duration overflow (security/robustness).
Confirmed:MessageIn.TTLis*uint64(unbounded). Casting totime.Duration(int64) without validation will silently overflow for large values. The//nolint:goseccomment lacks justification. Implement the suggested bounds check inprepareMessage:validUntil := message.ValidUntil if message.TTL != nil && *message.TTL > 0 {
//nolint:gosec // not a problemvalidUntil = anys.AsPointer(time.Now().Add(time.Duration(*message.TTL) * time.Second),)
const maxTTLSeconds = 10 * 365 * 24 * 60 * 60 // ~10 yearsif *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), })
Enqueue returns non-nil state when insert fails.
Confirmed: Line 200 returns populatedstatealongside error, which is unconventional. While the handler (3rdparty.go:135) correctly ignores state on error, the pattern violates Go conventions. Change to returnnilon Insert failure:if insErr := s.messages.Insert(msg); insErr != nil {
return state, insErr
}return nil, insErr
🧹 Nitpick comments (13)
internal/sms-gateway/modules/db/health.go (1)
17-23: Redundant explicit zero-value initialization.
atomic.Int64is zero-initialized by default in Go, sofailedPings: atomic.Int64{}is unnecessary. That said, keeping it explicit for clarity is a valid stylistic choice.func newHealth(db *sql.DB) *health { return &health{ - db: db, - - failedPings: atomic.Int64{}, + db: db, } }internal/sms-gateway/modules/push/upstream/client.go (2)
29-35: Consider configuring HTTP client timeout.The
http.Clientis created without a timeout, which could cause requests to hang indefinitely. Consider setting a reasonable timeout:- c.client = &http.Client{} + c.client = &http.Client{ + Timeout: 30 * time.Second, + }
105-112: Consider closing idle connections before releasing the client.Setting
c.client = nildoesn't close underlying TCP connections. For proper resource cleanup, callCloseIdleConnections():func (c *Client) Close(_ context.Context) error { c.mux.Lock() defer c.mux.Unlock() + if c.client != nil { + c.client.CloseIdleConnections() + } c.client = nil return nil }internal/worker/locker/mysql.go (1)
29-29: Optional: Redundant explicit zero-value initialization.The explicit
sync.Mutex{}initialization is redundant since Go zero-initializes struct fields. However, this is purely stylistic and doesn't affect functionality.Consider simplifying to:
- mu: sync.Mutex{},The field will be automatically zero-initialized, which is the correct state for an unused mutex.
internal/sms-gateway/handlers/mobile.go (1)
275-276: Add comment explaining ignored error or handle it.The past review comment (marked as addressed) suggested either handling the error from
nanoid.Standard()or adding a comment explaining why it's safe to ignore. The error is still being ignored without explanation.While the error cannot occur with size 21 and the default alphabet, a brief comment would clarify this for future maintainers:
const idGenSize = 21 + // nanoid.Standard with size 21 never returns an error idGen, _ := nanoid.Standard(idGenSize)Alternatively, handle the error defensively:
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/pubsub/module.go (1)
20-22: Remove the no-op OnStart function.The OnStart hook that returns nil serves no purpose. When OnStart is not needed, you can omit the field entirely or set it to nil directly.
Apply this diff:
lc.Append(fx.Hook{ - OnStart: func(_ context.Context) error { - return nil - }, OnStop: func(_ context.Context) error {internal/sms-gateway/modules/messages/workers.go (1)
32-32: Remove redundant explicit initialization.The explicit
sync.Mutex{}initialization is redundant since the zero value ofsync.Mutexis already a valid, unlocked mutex.Apply this diff:
queue: map[uint64]struct{}{}, - mux: sync.Mutex{}, }internal/sms-gateway/modules/sse/module.go (1)
25-25: Consider omitting the explicit nil OnStart.When no startup logic is needed, the
OnStartfield can be omitted entirely rather than explicitly setting it to nil.Apply this diff:
fx.Invoke(func(lc fx.Lifecycle, svc *Service) { lc.Append(fx.Hook{ - OnStart: nil, OnStop: func(ctx context.Context) error {internal/sms-gateway/modules/settings/models.go (1)
10-25: DeviceSettings embedding and constructor look good; only optional consideration on map aliasingEmbedding
models.TimedModeland theNewDeviceSettingspartial constructor are consistent with the rest of the models and avoid exhaustruct noise. The direct reuse of thesettingsmap is efficient; if you ever need to protect stored settings from later caller mutation, consider making a shallow copy insideNewDeviceSettings.internal/sms-gateway/modules/sse/errors.go (1)
1-7: ErrNoConnection sentinel is fine; consider adding a brief doc commentThe exported
ErrNoConnectionsentinel is appropriate for signaling missing SSE connections and works well witherrors.Is. For discoverability, you might add a short comment like// ErrNoConnection indicates that no SSE connection exists for the given key.above it.internal/sms-gateway/modules/settings/repository.go (2)
29-61: UpdateSettings transactional merge looks solid; minor nit on return value in error pathsThe transactional flow—locking the existing row by
user_id, normalizingsource.Settings, merging withappendMap, and upserting viaOnConflict{UpdateAll: true}—is a good pattern for avoiding lost updates on concurrent writes.One minor clarity nit: on error, you return
updatedSettingsalong with the wrapped error, but for most failure pathsupdatedSettingswill still be nil (and in the rare case of a commit‑time error, it will contain the desired state but not actually be persisted). If callers never inspect the value whenerr != nil, you’re fine; otherwise, consider returningnilin the error case to make that contract explicit.
63-76: ReplaceSettings implementation is straightforward; consider nil value on error for clarityWrapping
tx.Save(settings)in a transaction and standardizing the error message as"failed to replace settings: %w"is fine. As withUpdateSettings, if you want to tighten the API contract, you could choose to returnnilalongside the error instead ofsettingswhenerr != nil, to avoid any implication that the returned value reflects persisted state.internal/sms-gateway/handlers/settings/mobile.go (1)
49-57: Good: PII kept in logs, generic 500 to clientSwitching from a formatted error (with IDs) to structured logging plus
fiber.NewError(500, "failed to get settings")avoids leaking device/user IDs in responses while preserving context in logs. If you care about consistency with other handlers, consider capitalizing the message (“Failed to get settings”), but that’s cosmetic.
📜 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 (91)
api/requests.http(0 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/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)test/e2e/mobile_test.go(1 hunks)test/e2e/utils_test.go(1 hunks)
💤 Files with no reviewable changes (3)
- go.mod
- internal/sms-gateway/modules/messages/service_test.go
- api/requests.http
✅ Files skipped from review due to trivial changes (1)
- internal/worker/config/config.go
🚧 Files skipped from review as they are similar to previous changes (33)
- internal/sms-gateway/modules/sse/metrics.go
- internal/sms-gateway/modules/devices/repository_filter.go
- internal/sms-gateway/modules/push/fcm/errors.go
- test/e2e/utils_test.go
- internal/sms-gateway/modules/push/client.go
- internal/sms-gateway/handlers/webhooks/mobile.go
- internal/sms-gateway/online/metrics.go
- cmd/sms-gateway/main.go
- internal/sms-gateway/modules/settings/utils.go
- internal/sms-gateway/cache/errors.go
- internal/sms-gateway/modules/push/service.go
- internal/sms-gateway/modules/webhooks/module.go
- internal/sms-gateway/handlers/base/handler_test.go
- internal/sms-gateway/modules/webhooks/repository_filter.go
- internal/sms-gateway/modules/push/fcm/client.go
- test/e2e/mobile_test.go
- internal/sms-gateway/handlers/logs/3rdparty.go
- internal/sms-gateway/models/models.go
- internal/sms-gateway/handlers/root.go
- internal/sms-gateway/models/migration.go
- internal/sms-gateway/modules/push/module.go
- internal/sms-gateway/modules/events/service.go
- internal/worker/config/types.go
- internal/sms-gateway/modules/settings/service.go
- internal/sms-gateway/modules/db/module.go
- internal/worker/executor/metrics.go
- internal/sms-gateway/online/service.go
- internal/sms-gateway/handlers/webhooks/3rdparty.go
- internal/sms-gateway/modules/events/types.go
- internal/sms-gateway/modules/webhooks/repository.go
- internal/sms-gateway/handlers/module.go
- internal/sms-gateway/handlers/health.go
- internal/sms-gateway/cache/factory.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/metrics/module.gointernal/sms-gateway/pubsub/module.gointernal/worker/app.gointernal/sms-gateway/modules/events/module.gointernal/sms-gateway/modules/settings/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.gointernal/sms-gateway/modules/messages/service.go
🧬 Code graph analysis (35)
internal/sms-gateway/pubsub/pubsub.go (4)
internal/config/config.go (2)
Config(10-20)PubSub(84-87)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/settings/models.go (4)
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/modules/messages/models.go (1)
Migrate(164-169)internal/sms-gateway/modules/webhooks/models.go (1)
Migrate(38-43)
internal/sms-gateway/handlers/events/mobile.go (5)
internal/sms-gateway/handlers/messages/mobile.go (1)
MobileController(29-33)internal/sms-gateway/handlers/settings/mobile.go (1)
MobileController(14-19)internal/sms-gateway/handlers/webhooks/mobile.go (1)
MobileController(15-19)internal/sms-gateway/models/models.go (1)
Device(34-45)internal/sms-gateway/handlers/base/handler.go (1)
Handler(15-18)
internal/sms-gateway/modules/metrics/module.go (7)
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/push/module.go (1)
Module(11-36)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/modules/sse/config.go (1)
Option(5-5)
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/messages/converters.go (1)
internal/sms-gateway/modules/messages/domain.go (1)
MessageOut(25-29)
internal/sms-gateway/modules/push/fcm/utils.go (4)
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/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/handlers/messages/3rdparty.go (4)
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/devices/repository.go (3)
ErrNotFound(14-14)ErrInvalidFilter(15-15)ErrMoreThanOne(16-16)
internal/sms-gateway/modules/webhooks/service.go (2)
internal/sms-gateway/modules/push/types.go (1)
Event(17-17)internal/sms-gateway/modules/webhooks/errors.go (1)
ErrInvalidEvent(9-9)
internal/sms-gateway/handlers/middlewares/userauth/userauth.go (1)
internal/sms-gateway/models/models.go (1)
User(18-24)
internal/sms-gateway/handlers/settings/3rdparty.go (4)
internal/sms-gateway/handlers/logs/3rdparty.go (2)
NewThirdPartyController(24-31)ThirdPartyController(20-22)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/settings/models.go (1)
DeviceSettings(10-17)
internal/sms-gateway/modules/webhooks/models.go (4)
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/messages/models.go (1)
Migrate(164-169)internal/sms-gateway/modules/settings/models.go (1)
Migrate(27-32)
internal/sms-gateway/modules/db/health.go (1)
pkg/health/types.go (2)
Checks(67-67)Provider(69-75)
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/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/config/module.go (3)
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/auth/service.go (1)
Config(20-23)internal/sms-gateway/handlers/config.go (1)
Config(3-11)
internal/sms-gateway/app.go (9)
internal/config/module.go (1)
Module(23-140)internal/sms-gateway/modules/db/module.go (1)
Module(16-26)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/webhooks/module.go (1)
Module(9-20)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/sms-gateway/modules/push/upstream/client.go (1)
internal/sms-gateway/modules/push/client/types.go (2)
Client(9-13)Message(15-18)
internal/sms-gateway/modules/events/module.go (8)
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/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/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/push/client/types.go (1)
internal/sms-gateway/modules/messages/models.go (1)
Message(36-56)
internal/worker/locker/mysql.go (1)
internal/worker/locker/locker.go (1)
ErrLockNotAcquired(9-9)
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/settings/mobile.go (4)
internal/sms-gateway/handlers/base/handler.go (1)
Handler(15-18)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/messages/models.go (1)
internal/sms-gateway/models/models.go (1)
SoftDeletableModel(12-16)
internal/sms-gateway/handlers/devices/3rdparty.go (4)
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/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/module.go (7)
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/metrics/module.go (1)
Module(9-19)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/modules/settings/service.go (1)
NewService(27-35)
internal/worker/executor/service.go (1)
internal/worker/config/types.go (1)
Duration(11-11)
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/handlers/mobile.go (1)
internal/sms-gateway/models/models.go (2)
Device(34-45)User(18-24)
internal/sms-gateway/modules/messages/errors.go (1)
internal/sms-gateway/modules/webhooks/errors.go (1)
ValidationError(12-16)
internal/sms-gateway/modules/messages/repository_filter.go (4)
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)WithUserID(13-17)WithDeviceID(41-46)internal/sms-gateway/modules/messages/models.go (1)
ProcessingState(13-13)internal/sms-gateway/handlers/middlewares/deviceauth/deviceauth.go (1)
WithDevice(83-87)
internal/sms-gateway/modules/messages/repository.go (2)
internal/sms-gateway/modules/messages/repository_filter.go (2)
SelectFilter(16-23)Order(7-7)internal/sms-gateway/modules/messages/models.go (3)
Message(36-56)ProcessingStatePending(17-17)MessageRecipient(139-145)
internal/sms-gateway/modules/settings/repository.go (1)
internal/sms-gateway/modules/settings/models.go (1)
DeviceSettings(10-17)
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)
⏰ 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)
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 (3)
test/e2e/webhooks_test.go (3)
118-119: Avoid variable shadowing.The variable
errof typeerrorResponseis shadowed by the error return fromjson.Unmarshal. Use distinct names (e.g.,errRespfor the response struct) to improve clarity.Apply this diff to fix the shadowing:
- var err errorResponse - if err := json.Unmarshal(response.Body(), &err); err != nil { + var errResp errorResponse + if err := json.Unmarshal(response.Body(), &errResp); err != nil { t.Fatal(err) } - if err.Message == "" { + if errResp.Message == "" { t.Error("expected error message in response") }Also applies to: 142-143
306-307: Avoid variable shadowing (same issue as in TestWebhooks_Get).Use distinct variable names for the error response struct to avoid shadowing the error from
json.Unmarshal.Apply the same pattern as suggested for TestWebhooks_Get:
- var err errorResponse - if err := json.Unmarshal(response.Body(), &err); err != nil { + var errResp errorResponse + if err := json.Unmarshal(response.Body(), &errResp); err != nil { t.Fatal(err) } - if err.Message == "" { + if errResp.Message == "" { t.Error("expected error message in response") }Also applies to: 333-334, 361-362, 390-391, 418-419, 446-447
174-476: Consider adding cleanup for created webhooks.The test creates multiple webhooks across different cases but doesn't clean them up. Adding
t.Cleanup()calls to delete created webhooks would prevent test pollution and ensure test isolation.Example pattern to add in successful creation test cases:
validate: func(t *testing.T, response *resty.Response) { var result webhook if err := json.Unmarshal(response.Body(), &result); err != nil { t.Fatal(err) } // Add cleanup t.Cleanup(func() { authorizedClient.R().Delete("webhooks/" + result.ID) }) // ... existing validation }
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (1)
test/e2e/webhooks_test.go(1 hunks)
⏰ 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 (2)
test/e2e/webhooks_test.go (2)
1-15: LGTM!The imports and webhook data model are appropriate for the e2e testing requirements.
549-556: LGTM! Good variable naming.These validation functions correctly avoid shadowing by using
errRespfor the response struct anderrfor the unmarshal error. This is the pattern that should be used throughout.Also applies to: 569-576
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/webhooks/service.go (1)
72-90: Good security improvement with device ownership validation.The signature change to accept a pointer and the addition of device ownership validation strengthen the API's security posture. The validation correctly checks if the device exists and belongs to the user before proceeding.
However, the error message at line 85 could be more descriptive:
ok, err := s.devicesSvc.Exists(userID, devices.WithID(*webhook.DeviceID)) if err != nil { - return fmt.Errorf("failed to select devices: %w", err) + return fmt.Errorf("failed to verify device ownership: %w", err) }
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (2)
internal/sms-gateway/handlers/webhooks/3rdparty.go(4 hunks)internal/sms-gateway/modules/webhooks/service.go(5 hunks)
🧰 Additional context used
🧬 Code graph analysis (2)
internal/sms-gateway/handlers/webhooks/3rdparty.go (6)
internal/sms-gateway/handlers/logs/3rdparty.go (2)
NewThirdPartyController(24-31)ThirdPartyController(20-22)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/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/webhooks/service.go (2)
internal/sms-gateway/modules/webhooks/models.go (1)
Webhook(11-25)internal/sms-gateway/modules/webhooks/errors.go (1)
ErrInvalidEvent(9-9)
⏰ 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)
🔇 Additional comments (7)
internal/sms-gateway/modules/webhooks/service.go (3)
56-56: LGTM: Error message standardization.The updated error message follows a consistent "failed to..." pattern, improving error reporting uniformity across the codebase.
92-102: LGTM: Clean factory pattern and consistent error handling.The use of the
newWebhookfactory function properly encapsulates model construction, and the repository interaction correctly passes the model value. Error handling follows the standardized pattern.
114-114: LGTM: Consistent error message updates.Error messages in Delete and notifyDevices paths now follow the standardized "failed to..." pattern for better consistency.
Also applies to: 126-126
internal/sms-gateway/handlers/webhooks/3rdparty.go (4)
32-40: LGTM: Constructor placement follows convention.Moving the constructor to the top of the file improves code organization and aligns with Go conventions. The dependency injection pattern using fx.In is properly implemented.
52-59: LGTM: Consistent error messaging.The standardized error message improves consistency across the codebase.
76-92: LGTM: Consistent pointer-based flow and improved error handling.The change to pointer-based DTO handling aligns with the service signature change and maintains proper flow:
- Creates pointer DTO
- Parses and validates in place
- Passes pointer to service
The updated error handling using
fiber.NewErrorprovides clearer HTTP error responses.
105-114: LGTM: Error message standardization.The updated error message maintains consistency with the broader error handling pattern across the codebase.
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)
test/e2e/webhooks_test.go (1)
208-213: Avoidt.Fatalin cleanup functions.Using
t.Fatalin cleanup functions registered witht.Cleanupcan prevent other cleanup functions from running if this one fails. Consider usingt.Errorinstead, or simply ignore cleanup errors since they're non-critical.Apply this pattern to all cleanup functions (also at lines 251-256, 296-301):
t.Cleanup(func() { - _, err := authorizedClient.R().Delete("webhooks/" + result.ID) - if err != nil { - t.Fatal(err) - } + authorizedClient.R().Delete("webhooks/" + result.ID) + // Ignore errors in cleanup - resource may already be deleted })
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (2)
internal/sms-gateway/modules/webhooks/service.go(5 hunks)test/e2e/webhooks_test.go(1 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
internal/sms-gateway/modules/webhooks/service.go (3)
internal/sms-gateway/modules/webhooks/models.go (1)
Webhook(11-25)internal/sms-gateway/modules/webhooks/errors.go (1)
ErrInvalidEvent(9-9)internal/sms-gateway/modules/devices/repository.go (1)
ErrNotFound(14-14)
⏰ 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)
🔇 Additional comments (10)
internal/sms-gateway/modules/webhooks/service.go (5)
72-74: LGTM - Pointer parameter and improved validation.The change to accept a pointer parameter is correct since the method mutates
webhook.IDat line 78. The event validation now usesErrInvalidEventand passeswebhook.Eventdirectly, which is cleaner than the previous approach.
82-90: Excellent security improvement - device ownership validation.The added device ownership verification prevents users from creating webhooks for devices they don't own. The implementation correctly checks for nil
DeviceID, verifies ownership, and returns appropriate validation errors.
92-98: LGTM - Helper function improves maintainability.Replacing the manual struct literal with the
newWebhookhelper function is a good refactoring that improves code maintainability and reduces duplication.
56-126: LGTM - Error message consistency improvements.All error messages have been updated from "can't X" to "failed to X", improving consistency and professionalism across the codebase.
100-101: ****The code is correct. The
newWebhook()function returns*Webhook(a pointer), not a value. SinceRepository.Replace()expectswebhook *Webhook, the call at line 100 correctly passes a pointer, not a value. There is no type mismatch.Likely an incorrect or invalid review comment.
test/e2e/webhooks_test.go (5)
17-172: LGTM - Comprehensive GET endpoint tests.The test function covers all important scenarios: empty list, populated list, and authentication errors. The table-driven test structure is clean and follows Go best practices.
174-497: LGTM - Comprehensive POST endpoint tests with excellent coverage.The test function covers all important scenarios: valid creation with various configurations, validation errors, and authentication errors. The table-driven structure is clean and maintainable.
510-539: LGTM - Previous issues have been resolved.The webhook deletion test has been properly fixed:
setup()now returns the server-generated webhook ID- The POST request no longer includes an ID in the body
- The
request()function correctly receives and uses the webhook ID- All scope issues have been resolved
The implementation now correctly tests deletion of an actual webhook resource.
540-597: LGTM - DELETE error cases are well covered.The tests correctly cover non-existent resource deletion (idempotent behavior) and authentication errors. The hardcoded "non-existent-id" at line 548 is appropriate for testing the non-existent resource case.
600-625: LGTM - Cleanup logic is defensive and correct.The cleanup logic properly checks if a webhook was created before attempting to delete it. Ignoring errors during cleanup is acceptable since the API supports idempotent DELETE operations (returns 204 even for non-existent resources).
0acb43d to
71fb64f
Compare
Summary by CodeRabbit
New Features
Improvements
Bug Fixes
Chores
✏️ Tip: You can customize this high-level summary in your review settings.