Skip to content

Conversation

@capcom6
Copy link
Member

@capcom6 capcom6 commented Nov 4, 2025

Summary by CodeRabbit

  • New Features

    • Added liveness, readiness and startup health endpoints; API spec enriched and product title updated.
    • Multi-arch release tooling and container build/publish flow added for PRs and releases; expanded release artifacts.
  • Bug Fixes

    • Standardized and clearer user-facing error messages and validation responses.
    • Improved reliability for streaming (SSE), push delivery, message enqueue/export, and device/device/message selection/resilience.
  • Chores

    • Introduced centralized linting, CI/release overhaul, expanded Makefile targets and developer tooling.

✏️ Tip: You can customize this high-level summary in your review settings.

@coderabbitai
Copy link

coderabbitai bot commented Nov 4, 2025

Walkthrough

Removed legacy Docker build/publish workflows and added Goreleaser-based CI; introduced comprehensive lint/config files; large API and implementation refactors across modules (module factory functions, push client abstraction, exported cache/pubsub types, messages/auth/signature changes, added sentinel errors, constructors, and model/migration updates); many handler and test adjustments.

Changes

Cohort / File(s) Summary
CI & release
/.github/workflows/docker-build.yml, /.github/workflows/docker-publish.yml, /.github/workflows/pr.yml, /.github/workflows/release.yml, /.github/workflows/go.yml, /.goreleaser.yaml, Dockerfile.goreleaser, Makefile
Deleted legacy docker-build/publish workflows; added PR/release goreleaser workflows and Dockerfile.goreleaser; updated Makefile targets and Go workflow steps.
Linters & repo config
/.golangci.yml, /.gitignore
Added detailed golangci-lint config and minor .gitignore formatting tweak.
Module factory conversions
internal/*/module.go, internal/config/module.go, internal/sms-gateway/*/module.go, pkg/health/module.go, internal/sms-gateway/modules/metrics/module.go, internal/sms-gateway/modules/sse/module.go
Replaced package-level exported Module variables with func Module() fx.Option factory functions across many packages.
Push abstraction & clients
internal/sms-gateway/modules/push/*, internal/sms-gateway/modules/push/client/*, internal/sms-gateway/modules/push/fcm/*, internal/sms-gateway/modules/push/upstream/*
Introduced client.Client interface, newClient factory and ErrInvalidPushMode; refactored fcm/upstream adapters and push service to depend on the client interface and updated Open/Send/Close signatures and lifecycle wiring.
Cache & PubSub public API
pkg/cache/*.go, pkg/pubsub/*.go, pkg/pubsub/*, internal/sms-gateway/pubsub/*
Exported/renamed types (MemoryCache, RedisCache, MemoryPubSub, RedisPubSub), updated constructors/receivers, adjusted options.apply signatures, and added sentinel errors (ErrInvalidConfig, ErrInvalidScheme).
Messages domain & handlers
internal/sms-gateway/modules/messages/*, internal/sms-gateway/handlers/messages/*, internal/sms-gateway/handlers/messages/params.go
Renamed domain types (MessagesOrder→Order), added SelectFilter/SelectOptions with builder methods, introduced constructors, changed repository/service/handler signatures and behaviors (Enqueue/GetState/UpdateState), and updated migrations/models (soft-delete embedding).
Auth subsystem
internal/sms-gateway/modules/auth/*
Renamed AuthCodeOneTimeCode; repository/service methods now return pointers; added ErrAuthorizationFailed, a usersCache wrapper, and Service.Run(ctx); adjusted auth flows and constructors.
SSE & events
internal/sms-gateway/modules/sse/*, internal/sms-gateway/modules/events/*
Added ErrNoConnection and ErrValidationFailed; refactored SSE streaming into handleStream, introduced buffer-size constant, improved metrics/labels, and updated event processing/error wrapping and timeouts.
Settings, webhooks, devices & handlers
internal/sms-gateway/modules/settings/*, internal/sms-gateway/modules/webhooks/*, internal/sms-gateway/modules/devices/*, internal/sms-gateway/handlers/*
Embedded timed/deletable models; added constructors (NewDeviceSettings, newWebhook, many New*Controller functions); removed fx.In usage in handlers; added devices.GetAny; standardized pointer DTO parsing and error messages.
Health system & types
pkg/health/*, internal/sms-gateway/modules/db/health.go, internal/sms-gateway/openapi/docs.go
Renamed HealthProviderProvider, added ObservedUnit/ObservedValue fields, adjusted thresholds/visibility, made NewHealth unexported, and expanded OpenAPI health endpoints and enum metadata.
Models & migrations
internal/sms-gateway/models/*, internal/sms-gateway/models/migration.go, internal/sms-gateway/modules/settings/models.go
Added SoftDeletable/TimedModel embeddings, added constructors (NewUser, NewDevice, NewDeviceSettings), switched AutoMigrate to new(Type) and wrapped migration errors.
Handlers & middleware
internal/sms-gateway/handlers/*, internal/sms-gateway/handlers/middlewares/userauth/userauth.go, internal/sms-gateway/app.go
Replaced fx.In-based constructors with explicit constructors accepting services/logger/validator; changed GetUser to return *models.User; updated handler signatures and app wiring to use Module() functions.
Tests & benchmarks
pkg/cache/*_test.go, pkg/cache/*_bench*.go, pkg/cache/*_concurrency*.go, test/e2e/*, internal/sms-gateway/modules/messages/service_test.go
Modernized tests (use errors.Is, context.Background, range loops), removed some unit tests, and adjusted formatting/nil-handling.
Misc errors & small edits
assorted internal/**, pkg/**
Normalized error wording ("can't"→"failed"), added sentinel errors, wrapped errors with %w, added nolint directives, small refactors and stylistic changes across many files.

Sequence Diagram(s)

sequenceDiagram
    participant FX as FX App
    participant Module as Module() factory
    participant NewClient as newClient()
    participant Client as client.Client
    participant PushSvc as Push Service

    Note right of Module `#DFF1DF`: Module() factories replace package vars
    FX->>Module: call Module()
    Module-->>FX: provide configs, services, clients

    Note right of NewClient `#FFF3DE`: construct concrete client (fcm/upstream)
    FX->>NewClient: provide push.Config
    NewClient-->>Client: return concrete client or error

    Note right of PushSvc `#F3E8FF`: runtime lifecycle
    FX->>PushSvc: OnStart -> open client
    PushSvc->>Client: Open(ctx)
    PushSvc->>Client: Send(ctx, []client.Message)
    Client-->>PushSvc: results/errors
    FX->>Client: Close(ctx) on stop
Loading

Estimated code review effort

🎯 5 (Critical) | ⏱️ ~120+ minutes

Areas warranting focused review:

  • Module var → Module() conversions: verify all call sites, fx.As/fx.Provide types, and init/migration side-effects.
  • Push client abstraction and lifecycle: new client.Client interface, newClient factory, fcm/upstream adapters, and lifecycle hooks (Open/Close) correctness.
  • Messages and auth API changes: repository/service signature changes, new SelectFilter/SelectOptions, Enqueue/GetState/UpdateState behavior and migrations.
  • Cache & PubSub renames: exported type renames and options.apply signature changes (consumer impact).
  • Database migrations/models: embedding SoftDeletable/TimedModel and AutoMigrate adjustments may affect schema and data; check migrations.
  • Tests removed/changed: ensure removed tests are intentional and new test patterns cover critical behavior.

Possibly related PRs

Suggested labels

deployed

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 23.36% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title '[build] migrate to GoReleaser' clearly and accurately reflects the main change, which is the migration of the build system to GoReleaser infrastructure.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch actions/migrate-to-goreleaser

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link

@coderabbitai coderabbitai bot left a 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.options is 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 the WHERE placeholder to avoid broken queries.

Where("user_id", settings.UserID) drops the = ? placeholder, so GORM builds WHERE 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 gofmt or gofumpt).

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 error
internal/sms-gateway/handlers/middlewares/userauth/userauth.go (1)

94-100: Consider storing pointers in Locals to reduce allocations.

The current pattern stores models.User values in Locals (lines 53, 80) then converts to *models.User by taking the address of a local copy. This causes the value to escape to the heap.

For better performance, consider storing *models.User directly 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 = nil

Though 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-15 as 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

📥 Commits

Reviewing files that changed from the base of the PR and between 9871e6a and f0de0ec.

⛔ Files ignored due to path filters (1)
  • go.sum is 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.go
  • internal/sms-gateway/modules/settings/module.go
  • internal/sms-gateway/pubsub/module.go
  • internal/sms-gateway/modules/events/module.go
📚 Learning: 2025-07-22T00:31:28.796Z
Learnt from: capcom6
Repo: android-sms-gateway/server PR: 154
File: internal/sms-gateway/modules/sse/service.go:67-79
Timestamp: 2025-07-22T00:31:28.796Z
Learning: In the SSE service Send method (internal/sms-gateway/modules/sse/service.go), blocking on full channels is intentional and acceptable. Timeout control is handled at the calling code level through context, not within the Send method itself. The user prefers this approach over adding a default case that would drop messages when channels are full.

Applied to files:

  • internal/sms-gateway/modules/events/service.go
  • internal/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 ID to id follows 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 Set method, and the error wrapping provides helpful context. Using jsonErr appropriately 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 ErrInvalidConfig sentinel error follows Go best practices and enables callers to use errors.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 ErrInvalidConfig enables callers to use errors.Is() for configuration error checking.


53-53: LGTM: Proper error wrapping with sentinel error.

Wrapping invalid scheme errors with ErrInvalidConfig maintains 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. Since cache.NewMemory(0) returns a new *memoryCache with its own items map, the events and blacklist caches are completely isolated—no collisions occur.

The name parameter 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 suggested cache.NewMemoryWithPrefix constructor 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 for init() 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 use Module() fx.Option patterns (as seen in other modules), consider registering the migration within the Module() function using fx.Invoke instead.

Could you clarify:

  1. What "backward compatibility" this init() maintains?
  2. 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 ownedClient is 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.go and 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 TimedModel to the top follows Go conventions and improves readability by separating embedded fields from regular fields.


20-20: LGTM!

The change from &DeviceSettings{} to new(DeviceSettings) is functionally equivalent and aligns with the pattern in internal/sms-gateway/modules/messages/models.go.

Note: There's inconsistency in migration patterns across modules. For example, internal/sms-gateway/modules/webhooks/models.go:26 uses &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.New for simple error strings and http.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 stable

Declaring 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 contract

Documenting 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 failures

Adding 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 wiring

Switching to explicit parameters while still seeding base.Handler with 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 explicit

These 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 newClient provider 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.Event to client.Event is 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 AuthCode to OneTimeCode better 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=1 ensures 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:

  1. The restore step will use the restore-keys fallback since exact matches won't exist
  2. Multiple cache entries will accumulate per branch
  3. 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_id from 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/v2 with the gosec nolint 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 ErrLockFailed sentinel error and the ValidationError type 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 use Module() 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 reassign nolint is justified here as cli.DefaultCommand is 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 SoftDeletableModel adds soft deletion tracking to messages, which is a useful feature for maintaining historical data.


71-71: LGTM! Appropriate nilnil directives.

The nilnil nolint 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.Errorf with %w for 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 of shErr to 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 DeviceID field is intentionally set to "" because the source struct messages.MessageOut does not have a DeviceID field. 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 ErrValidationFailed keeps 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 ErrInitializationFailed wrapping and the move to client.Message keep 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: write permission 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 build to goreleaser and 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., MessagesSelectFilterSelectFilter) improves code clarity since these types are already in the messages package 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 GetAny method 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 Enqueue method signature expects device 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.NewError standardizes 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.go and handlers/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.

Copy link

@coderabbitai coderabbitai bot left a 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: Preserve created_at when upserting.

DeviceSettings embeds models.TimedModel, so calling Create with clause.OnConflict{UpdateAll: true} rewrites every column on conflict — including created_at. On every UpdateSettings run 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 from source before 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 uses actions/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 @v6 across both jobs:

  - name: Set up Go
    uses: actions/setup-go@v5
+   uses: actions/setup-go@v6

Also 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_URL as 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 Config struct already controls upstream behavior (line 91), consider adding UpstreamRateLimit and UpstreamRateWindow fields 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

📥 Commits

Reviewing files that changed from the base of the PR and between f0de0ec and 5e56b3d.

📒 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: read block 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: read for checkout, pull-requests: write for commenting, and packages: write for 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:nilnil annotation 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 DeviceActiveWithin field was changed from uint to int. While the validation min=1 prevents negative values, uint is 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 MessagesSelectFilter to SelectFilter aligns with the broader type renaming refactoring. The method logic remains correct.


58-76: LGTM!

The return type update to SelectOptions is consistent with the refactoring. The introduction of the maxLimit constant provides defensive programming, even though Line 20's validation already enforces max=100. This redundancy is acceptable as a safety measure.


78-80: LGTM!

The Order field type update from MessagesOrder to Order is consistent with the type renaming refactoring.


82-87: LGTM!

The return type update to messages.Order is 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) to fiber.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 GetUser correctly 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.

Copy link

@coderabbitai coderabbitai bot left a 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 v1 with the appropriate stable version tag you intend to use.)

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 5e56b3d and 8a517fa.

📒 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.

Copy link

@coderabbitai coderabbitai bot left a 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 with time.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 numCaches does not compile because range expects an array, slice, map, string, or channel. With numCaches defined 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

HTTL returns -1 when the hash field has no TTL. Adding ttlDelta on top of -1 means a 1-second extension turns into 0, expiring the item immediately. That regression breaks Get(..., 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 + ttlDelta
pkg/cache/memory_bench_test.go (3)

286-290: Revert to counting loop for benchmark prefill

for i := range bm.operationsPerGoroutine * bm.goroutines will not compile because range cannot 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 scalar size

Here too, range on an int is invalid Go. Use the classic for 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 numKeys is 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: 30 is quite permissive for a "very strict" config (line 7), especially paired with package-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 if max-complexity should be lowered to catch outliers more aggressively.


234-282: Review exhaustruct exclusion list for project specificity.

The exhaustruct.exclude list 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:

  1. These exclusions actually correspond to types used in this codebase.
  2. The exclusions don't inadvertently suppress legitimate struct initialization bugs.
  3. 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: Reconsider nakedret max-func-lines: 0.

Setting max-func-lines: 0 means 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

📥 Commits

Reviewing files that changed from the base of the PR and between 8a517fa and 64cbdd6.

📒 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.go
  • pkg/cache/memory_test.go
  • pkg/cache/memory_edge_test.go
  • pkg/cache/memory_profile_test.go
  • pkg/cache/redis.go
  • pkg/cache/memory.go
  • pkg/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.go
  • pkg/cache/redis.go
  • pkg/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: false for captLocal and line 321: skipRecvDeref: false for underef are 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.yml configuration 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) and testifylint (updated to 1.3.0) are confirmed in v2.5.0's changelog, and both testableexamples and testifylint are listed among golangci-lint's supported linters.

While copyloopvar requires 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 different apply() 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 in pkg/cache/options.go.

All call sites in the cache package (pkg/cache/memory.go, pkg/cache/redis.go) use apply() 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 in pkg/cache/memory.go and lines 147, 216, 247 in pkg/cache/redis.go). Chaining calls found in other modules use different apply() 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 numGoroutines syntax is cleaner, and renaming to getErr properly 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

Copy link

@coderabbitai coderabbitai bot left a 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:

  1. o.delete = true is set by AndDelete()
  2. o.isEmpty() returns false (line 152), skipping simple get
  3. No TTL cases match in the switch
  4. Default case executes simple HGet and returns (line 186)
  5. 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 ttlTimestamp and ttlDelta are both zero.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 64cbdd6 and ceae620.

📒 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 errors import is needed for the errors.Is() calls used to detect redis.Nil errors.


44-46: LGTM: Correct handling of negative TTL values.

Setting ttl = 0 when HTTL returns a negative value (key doesn't exist or has no expiry) prevents issues when adding the delta.


72-72: LGTM: Type properly exported.

The RedisCache type 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 ErrInvalidConfig allows callers to programmatically detect configuration errors.


136-137: LGTM: Self-documenting constant improves readability.

The itemsPerKey constant clarifies that HGETALL returns key-value pairs in a flat array.


156-157: LGTM: Proper sentinel error checking.

Using errors.Is() to check for redis.Nil is 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.

Copy link

@coderabbitai coderabbitai bot left a 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

📥 Commits

Reviewing files that changed from the base of the PR and between ceae620 and 6398a12.

📒 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 apply simplifies the API. Since this is an internal method and the PR compiles, all call sites have been appropriately updated.

Copy link

@coderabbitai coderabbitai bot left a 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: Guard Close against concurrent double-close panics.

Two goroutines calling Close at once can both fall through the non-blocking select and execute close(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: Synchronize Close to avoid double closing closeCh.

Concurrent callers can both take the default branch and close m.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: sets validUntil to the zero value time.Time{}, which typically means "no expiry"
  • ttl < 0: sets validUntil to time.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 == 0 mean "no expiry" or "expire immediately"?
  • Should ttl < 0 be rejected, or should it mean something specific?

The comparison bug for ttl < 0 has 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 using uint for semantic correctness.

Since the validation ensures min=1, using uint would be more semantically appropriate to express that only non-negative values are valid. However, if downstream dependencies require int, 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 directly
internal/sms-gateway/modules/health/types.go (1)

41-51: Consider removing or documenting the unreachable default case.

The switch covers all possible statusLevel values (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

📥 Commits

Reviewing files that changed from the base of the PR and between 6398a12 and 5deca89.

📒 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 errors package import is correctly added for the errors.New() usage on line 26.


32-56: LGTM: Type refactoring is correctly applied.

The return type change from messages.MessagesSelectFilter to messages.SelectFilter aligns 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.SelectOptions is 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 enforces max=100. However, this defensive approach is acceptable and doesn't cause issues.


78-87: No issues found. Code is correct.

The MessagesOrderLIFO constant is properly defined as type Order in internal/sms-gateway/modules/messages/repository_filter.go (line 11: MessagesOrderLIFO Order = "lifo"), which is fully compatible with the refactored type signature in params.go. The type change from messages.MessagesOrder to messages.Order has been consistently applied throughout the codebase.

pkg/cache/options.go (1)

12-16: No breaking change—all call sites use apply() 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) invoke apply() 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-fluent apply still covers current usage.

options.apply no 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 adding ErrInvalidConfig.

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 Checks field 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 ObservedUnit and ObservedValue fields follows standard health check patterns for exposing metrics.


69-75: LGTM - Simplified naming.

Renaming HealthProvider to Provider is 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 NewHealth to newHealth (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.As annotation correctly reflects the HealthProviderProvider interface rename.

internal/sms-gateway/modules/db/health.go (1)

60-60: LGTM - Updated interface assertion.

The compile-time assertion correctly reflects the HealthProviderProvider interface rename.

internal/sms-gateway/modules/health/service.go (3)

10-22: LGTM - Consistent type updates.

Field types correctly updated from HealthProvider to Provider to 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 Provider type update aligns with the interface rename.


71-87: LGTM - Consistent closure type updates.

All probe closures correctly updated to use the Provider type 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 gochecknoinits suppression is correctly applied with a clear explanation, as the init() 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 ErrInvalidEvent follows Go's sentinel error pattern, allowing callers to use errors.Is() for error checking.


34-36: LGTM - Improved error checking.

Using errors.As instead 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 NewRepository constructor 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.Option keeps the consumer API consistent while giving us an immutable module wiring; the call sites invoking Module() 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 shared ErrInvalidEvent keeps 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?

Copy link

@coderabbitai coderabbitai bot left a 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 since atomic.Int64 is 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: nil is unnecessary.

-		Device:     nil,
+		Device:     nil, // or simply omit if you prefer implicit zero-value

Or 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.RWMutex is 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 line

Alternatively, 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 tickerChan is 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

📥 Commits

Reviewing files that changed from the base of the PR and between fe24cc5 and a3b6840.

📒 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.go
  • internal/sms-gateway/modules/cleaner/module.go
  • internal/sms-gateway/modules/devices/module.go
  • internal/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.go
  • internal/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. Returning nil, nil for probes that don't report checks is reasonable, and the nolint:nilnil comments appropriately document this intentional pattern for empty results.

Also applies to: 58-60


62-62: LGTM! Interface assertion updated correctly.

The change from healthmod.HealthProvider to healthmod.Provider correctly 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 returns fiber.StatusBadRequest (400) for client-side validation failures.


150-168: LGTM: Improved error handling.

The function correctly returns 400 Bad Request for 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 of fiber.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 RegisterDevice method signature expects user models.User (a value type), not a pointer. The code correctly dereferences *user to 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 for math/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 AutoMigrate failure with module-specific context tightens observability without altering behavior. 👍

internal/sms-gateway/modules/sse/errors.go (1)

5-7: Good call introducing a sentinel.

ErrNoConnection gives 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 new newHTTPHandler.

internal/sms-gateway/modules/devices/module.go (1)

16-33: Devices module wiring preserved.

The Module() helper mirrors existing behavior, and the explicit fx.Out keeps 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 confirmed

Switching 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 factory

The 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 clarity

The 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 allocations

Using 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 intact

The 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 lo library is well-established for functional transformations and is used appropriately throughout this file.


77-88: LGTM: Method signature updated consistently.

The signature change from MessagesOrder to Order aligns with the broader type refactoring, and the default value handling is appropriate.


104-114: LGTM: Clean functional transformation.

The use of lo.MapToSlice appropriately converts the states map to a slice of MessageState structs. Setting ID: 0 is 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/MessagesSelectOptions to SelectFilter/SelectOptions aligns 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 cacheErr is 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 %w provides better error context and maintains the error chain for errors.Is checks.


288-292: LGTM: Cleaner functional implementation.

The refactor to lo.Map with the newMessageRecipient helper is more concise and maintains the same logic.


312-317: LGTM: Consistent helper usage.

The use of newMessageRecipient helper maintains consistency with the refactored recipientsToModel method.


352-361: LGTM: Custom error type for validation.

The use of ValidationError as a custom string type for phone validation errors is a valid Go pattern, assuming ValidationError implements the Error() method (standard for typed error patterns).


91-94: The *new(SelectFilter) pattern is valid but unconventional.

The builder methods (WithExtID, WithDeviceID) on SelectFilter use pointer receivers and return *SelectFilter. The code creates a pointer with new(), chains methods on it, then dereferences with * to pass a value to Get().

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.

Copy link

@coderabbitai coderabbitai bot left a 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 closeSignal channel can be closed twice:

  1. During service shutdown, Close() (line 113) closes all closeSignal channels
  2. This causes handleStream to return (line 171), triggering the deferred removeConnection (line 135)
  3. removeConnection then attempts to close the same closeSignal again (line 222), causing a panic

Closing an already-closed channel panics in Go.

Apply this diff to fix by removing the close from removeConnection (cleanup happens in Close() 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 closeSignal channel 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.RWMutex is 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

📥 Commits

Reviewing files that changed from the base of the PR and between a3b6840 and e7b28c6.

📒 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 ErrNoConnection using %w enable proper error inspection with errors.Is(). The multiline structured logging is also more readable.


120-131: LGTM! Good refactoring for separation of concerns.

Extracting the streaming logic into handleStream improves 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 %w wrapping 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 eventsBufferSize constant.

@capcom6 capcom6 force-pushed the actions/migrate-to-goreleaser branch from c3cf1a4 to a6dda30 Compare November 9, 2025 11:48
Copy link

@coderabbitai coderabbitai bot left a 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 the range loops to restore compilation.

range over an int (e.g., for i := range numCaches) does not compile in Go; range only 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: range over the int constants numCaches/10 fails to compile. Switch back to the standard for 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.goroutines is invalid because range cannot iterate an int. Please revert to the classic indexed loop (same applies to other added range loops in this file).

-			for i := range bm.operationsPerGoroutine * bm.goroutines {
+			for i := 0; i < bm.operationsPerGoroutine*bm.goroutines; i++ {

389-399: range size also breaks compilation.

Here size is an int, 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: numKeys is an int, so range cannot 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.

max isn’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 after userauth.GetUser(c) to ensure user safety.

The GetUser function can return nil, but line 111 dereferences it immediately without checking: login = user.ID. Even though HasUser returns true, there's no guarantee GetUser won'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 @master introduces 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 tag commands at lines 111-112 reference latest-arm64 and latest-amd64 tags that GoReleaser does not create. The .goreleaser.yaml configuration uses dockers_v2 with multi-platform builds, which produces a single multi-arch manifest with tags like latest, not separate per-architecture tags. This workflow will fail when these docker tag commands execute.

You need to either:

  1. Update .goreleaser.yaml to produce separate per-arch images with appropriate tags, or
  2. 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 %w will panic.

Line 185 still uses fmt.Errorf("%w: %w", ErrLockFailed, err). The fmt.Errorf function only supports one %w verb; 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...), nil is valid but potentially confusing. Since NewMemory returns only *MemoryPubSub without 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{} and sync.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_type label 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 ObserveEventDeliveryLatency method 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 since fx.Out is 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 put handler above, restoring make(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

📥 Commits

Reviewing files that changed from the base of the PR and between c3cf1a4 and a6dda30.

⛔ Files ignored due to path filters (1)
  • go.sum is 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.go
  • internal/sms-gateway/modules/metrics/module.go
  • internal/sms-gateway/modules/events/module.go
  • internal/sms-gateway/pubsub/module.go
  • internal/sms-gateway/modules/settings/module.go
  • internal/sms-gateway/modules/cleaner/module.go
  • internal/sms-gateway/modules/webhooks/module.go
  • internal/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.go
  • pkg/cache/memory.go
  • 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/memory_concurrency_test.go
  • pkg/cache/memory_test.go
  • pkg/cache/memory_profile_test.go
  • pkg/cache/memory.go
  • pkg/cache/memory_bench_test.go
  • 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/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)

Copy link

@coderabbitai coderabbitai bot left a 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...), nil is valid but unusual—it pairs a function result with a nil literal in a tuple assignment. For clarity, consider:

-		pubSub, err = pubsub.NewMemory(opts...), nil
+		pubSub = pubsub.NewMemory(opts...)

Since err is already nil from the successful url.Parse call, explicit reassignment is unnecessary. The post-switch error check (line 46) will correctly handle the nil case.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between a6dda30 and 52442d2.

📒 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:exhaustruct comment 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 ErrInvalidScheme as a sentinel error (line 17) and its use in error wrapping (line 43) enables better error inspection via errors.Is(). The post-switch error check (lines 46-48) properly handles constructor failures with contextual wrapping.

Also applies to: 43-43, 46-50

Copy link

@coderabbitai coderabbitai bot left a 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 t for error reporting.

While using the blank identifier is technically correct since this test relies solely on the race detector, test functions typically use t.Error or t.Fatal to report issues. If any of the cache operations encounter critical errors during setup or execution, those would be silently ignored.

Consider keeping the t parameter and adding minimal error checks for critical operations to improve test diagnostics.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 52442d2 and c902c35.

📒 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) to errors.Is(err, cache.ErrKeyNotFound) is a best practice that properly handles wrapped errors. The variable renaming to getErr also 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 key parameter 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.

@capcom6 capcom6 force-pushed the actions/migrate-to-goreleaser branch from f5d4a1d to 56b30fe Compare November 11, 2025 02:14
Copy link

@coderabbitai coderabbitai bot left a 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 Publish method holds a read lock from line 54 through the wg.Wait() at line 82. During this time, if a subscription is cancelled or closed, the cleanup goroutine in Subscribe will attempt to call unsubscribe, 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.

appendMap assigns into settings.Settings even 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 master branch, 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.0 with 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-arm64 and latest-amd64 names, but when GoReleaser runs with --snapshot, it automatically adds the platform suffix to tags. The exact tag format depends on your .goreleaser.yaml configuration. If GoReleaser does not produce latest-arm64 and latest-amd64 tags, the docker tag commands will fail.

Please verify the image tags that GoReleaser actually produces by inspecting your .goreleaser.yaml configuration. Confirm that the tags in dockers_v2 are configured to produce latest-arm64 and latest-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:

  1. Update .goreleaser.yaml to explicitly define per-arch tag templates, or
  2. 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 note

Line 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 constructor
internal/config/module.go (2)

27-37: Wrap private Config provider with fx.Annotate.

This issue was previously flagged. Passing fx.Private as a separate argument causes Fx to treat it as a constructor, leading to startup errors. Wrap the provider with fx.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.Path to /api whenever 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. While 21 is 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 %w wrapping.

This issue was previously flagged. Line 185 uses fmt.Errorf("%w: %w", ErrLockFailed, err). The fmt.Errorf function permits only one %w, so hitting this path will panic instead of returning an error. Drop the second %w (use %v instead).

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{} and sync.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 of atomic.Int64 is 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 GetUser now returning *models.User, a typed-nil or mismatched Locals entry keeps HasUser returning true even though GetUser yields nil. UserRequired would then pass the request while downstream handlers see a nil user and can panic on dereference. Please flip HasUser to rely on GetUser(c) != nil so 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 put method, 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 UnusedLifetime is 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

📥 Commits

Reviewing files that changed from the base of the PR and between f5d4a1d and 56b30fe.

⛔ Files ignored due to path filters (1)
  • go.sum is 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.go
  • 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/memory_edge_test.go
  • pkg/cache/memory_concurrency_test.go
  • pkg/cache/redis.go
  • pkg/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.go
  • internal/sms-gateway/modules/auth/module.go
  • internal/sms-gateway/modules/events/module.go
  • internal/config/module.go
  • internal/sms-gateway/modules/devices/module.go
  • internal/sms-gateway/modules/settings/module.go
  • internal/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.go
  • internal/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)

Copy link

@coderabbitai coderabbitai bot left a 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.RWMutex is already ready to use. Explicit initialization with sync.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., ErrorTypeWriteFailure and ErrorTypeFlushFailure) 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 ErrorTypeFlushFailure to the metrics constants.)

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 56b30fe and b31038a.

📒 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 HasUser benefits from GetUser's type-safe assertion logic.


94-100: LGTM! Improved nil safety with explicit pointer return.

The signature change from models.User to *models.User with explicit nil return provides a clear contract: the caller must check for nil rather 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 GetUser returns nil and 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 %w for proper error wrapping with ErrNoConnection, enabling errors.Is checks. The multiline log formatting also improves readability. The concurrency control with RLock is appropriate.


120-131: LGTM: Clean separation of concerns.

Delegating the streaming logic to handleStream improves 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 handleStream method correctly manages the connection lifecycle with deferred cleanup. The write error handling at lines 151-163 properly addresses the previous review concern—errors from writeToStream now cause handleStream to exit, triggering the deferred removeConnection and keeping metrics accurate.

The concern about negative keepAlivePeriod values is also addressed: WithKeepAlivePeriod() in config.go validates negative durations and replaces them with the default value, preventing any panic when time.NewTicker is called at line 141.

Copy link

@coderabbitai coderabbitai bot left a 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 calling t.Parallel() to run concurrently with other tests, potentially catching more race conditions. This would require keeping the t parameter.

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

📥 Commits

Reviewing files that changed from the base of the PR and between b31038a and c710e34.

📒 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.go
  • pkg/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 getErr avoids 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.

@capcom6 capcom6 force-pushed the actions/migrate-to-goreleaser branch from c710e34 to ac843cb Compare November 11, 2025 23:33
Copy link

@coderabbitai coderabbitai bot left a 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-only max here

max became 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 again

We just reintroduced the regression where ttl < 0 leaves validUntil set to time.Now(). The memory cache’s expiry check still uses now.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 constructor
internal/config/module.go (2)

26-37: Wrap private Config provider with fx.Annotate.

Passing fx.Private as a separate argument makes Fx treat it like a constructor, causing startup errors. Wrap the provider with fx.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.Path to /api whenever 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 .PHONY declarations into a single declaration.

Having two separate .PHONY declarations 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 .PHONY declaration, 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-clean

Also 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 nil check with errors.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.T parameter 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:3 without 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.20 or 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 | rev works correctly but is less readable than bash parameter expansion. For a tag ref like refs/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@v3 and setup-terraform@v2 have newer versions available (v4 and v3 respectively).

      - name: Install Terraform
        uses: hashicorp/setup-terraform@v2
+       # Consider: hashicorp/setup-terraform@v3
         with:
-          terraform_version: 1.4.6
+          terraform_version: 1.9.0
internal/sms-gateway/pubsub/module.go (1)

5-5: Good error wrapping improvement.

Adding the fmt import 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.Standard ever returns an error (size out of range, RNG init fault, library change), we silently keep a nil generator and the first h.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 tokenCacheTTL constant 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 settings map after invoking NewDeviceSettings, 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 client to nil and mux to sync.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{},
 	}, nil
internal/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

📥 Commits

Reviewing files that changed from the base of the PR and between c710e34 and ac843cb.

📒 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.go
  • internal/sms-gateway/modules/metrics/module.go
  • internal/sms-gateway/modules/webhooks/module.go
  • internal/sms-gateway/pubsub/module.go
  • internal/config/module.go
  • internal/sms-gateway/handlers/module.go
  • internal/sms-gateway/modules/settings/module.go
  • internal/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.go
  • pkg/cache/memory_concurrency_test.go
  • pkg/cache/redis.go
  • pkg/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.go
  • pkg/cache/redis.go
  • pkg/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.go
  • internal/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.go
  • 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 (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)

Copy link

@coderabbitai coderabbitai bot left a 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 Close method sets c.client = nil without acquiring the mutex, while Open (lines 29-35) and Send both access c.client under mutex protection. Concurrent calls to Close and Open/Send can 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 error

Line 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. While 21 is 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). AuthorizeUser uses username + "\x00" + password, but this code uses userID + currentPassword without 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 ErrInvalidConfig is consistent with the pattern in pkg/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 deploy and deploy-secondary jobs depend on goreleaser completing 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 (nil for 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

📥 Commits

Reviewing files that changed from the base of the PR and between ac843cb and 68ebce8.

📒 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.go
  • pkg/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.go
  • 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.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 nil initialization follows Go conventions—pointer variables default to nil.


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 declares ErrInvalidScheme.


19-51: Excellent error handling improvements.

The addition of consistent error wrapping throughout the New function 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 RedisPubSub type.

.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 .tf files and deployment scripts in deployments/docker-swarm-terraform will maintain compatibility.

Makefile (6)

2-3: Clean separation of registry and image variables.

The introduction of registry_name and image_name is 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 coverage target correctly depends on test and generates both summary and HTML reports.


39-41: Clean target properly removes goreleaser artifacts.

The updated clean target now removes coverage.*, benchmark.txt, and the dist/ 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 to ghcr.io/android-sms-gateway via Makefile variable.
  • RELEASE_ID: Referenced in .goreleaser.yaml ({{ .Env.RELEASE_ID }}) for the AppRelease ldflag; hardcoded to 0 for 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 errors package import is correctly added to support errors.Is() calls for robust error checking throughout the file.


44-46: LGTM: Defensive guard for negative TTL values.

The guard correctly handles cases where HTTL returns -1 (no expiration) or -2 (key doesn't exist), preventing negative values in subsequent TTL calculations.


72-79: LGTM: Type exported consistently.

The RedisCache type 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 ErrInvalidConfig follows the established pattern from pkg/pubsub/redis.go and enables proper error checking with errors.Is().


136-137: LGTM: Good optimization with capacity pre-allocation.

The itemsPerKey constant 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 RedisCache type.

internal/sms-gateway/modules/sse/service.go (7)

17-19: LGTM! Good practice to extract the magic number.

Defining eventsBufferSize as 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 %w enables 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 handleStream improves 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 handleStream method correctly addresses the previous review concern about write error handling. The success flag pattern (lines 150-163) ensures that when writeToStream fails, the error is captured and handleStream exits 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 %w wrapping provide clear context for debugging write and flush failures while preserving the error chain.


194-218: LGTM! Uses the new constant.

Properly uses eventsBufferSize constant 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 order is 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.MapToSlice migration 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.Map with the newMessageRecipient helper improves readability and eliminates manual slice construction.


286-312: LGTM! Refactor preserves hashing and state conversion logic.

The migration to the newMessageRecipient helper maintains the phone number hashing and state compatibility logic correctly.


341-357: LGTM! ValidationError correctly replaces ErrValidation.

The function properly uses ValidationError for 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.NewUser and 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.NewDevice and persists via devicesSvc.Insert, with appropriate error wrapping.


141-157: LGTM! Named timeout constant improves readability.

The function correctly wraps ErrAuthorizationFailed and 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 Run method 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 SelectFilter and SelectOptions aligns 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 Select and handles zero/multiple result cases appropriately.


142-174: LGTM! Transaction handling with appropriate error wrapping.

The function correctly wraps transaction errors and explicitly returns nil on 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.Event type 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 defaultDebounce constant and migration to client.Client interface improve code maintainability and flexibility.


43-73: LGTM - Consistent constructor refactoring.

The constructor correctly adopts the new client.Client interface and applies consistent error messaging patterns.


92-121: LGTM - Improved error handling clarity.

The setErr variable 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 to client.Message maintain consistency with the broader refactoring effort.

Copy link

@coderabbitai coderabbitai bot left a 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 leaking EOF.

fiber.Ctx.BodyParser returns io.EOF when the body is empty, so Line 58 exits early and the new check on Line 62 never runs. Clients now see a 400 with message EOF instead of the intended "Empty request", so the feature doesn’t work as advertised and exposes an internal parse error. Treat io.EOF as 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

Close now grabs c.mux and sets c.client = nil, but Send still reads c.client without coordination. If shutdown triggers Close while 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

📥 Commits

Reviewing files that changed from the base of the PR and between 68ebce8 and 41c3a20.

📒 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.yaml configuration. 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 previous needs.build.outputs.app-version approach.

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 HTTL returns -1 (field exists but has no TTL), clamping to 0 ensures that updateTTL extends 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 redisCache to RedisCache, making it part of the public API. This is consistent with similar exported types like RedisPubSub in pkg/pubsub/redis.go.


87-87: Excellent error handling improvements.

The addition of ErrInvalidConfig wrapping and the use of errors.Is for checking redis.Nil are 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 itemsPerKey constant 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 the delete field at line 62 (!o.delete). When delete=true, isEmpty() returns false, ensuring the code takes the Lua script path even without TTL options. The refactoring successfully resolves the past review concern.

Copy link

@coderabbitai coderabbitai bot left a 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 adding fetch-depth: 0 to 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

📥 Commits

Reviewing files that changed from the base of the PR and between 41c3a20 and 4720720.

📒 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 lo library and its usage for Map operations 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 ErrNoContent sentinel error, and line 244 correctly caches with the persisted msg.ExtID instead of the potentially empty message.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 UpdateState method (repository.go:150-152) uses OnConflict{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's UpdatedAt timestamp is never refreshed.

If the intent is to upsert (update or insert), consider replacing DoNothing with DoUpdates to preserve the latest timestamp:

Clauses(clause.OnConflict{
    UpdateAll: true,
}).Create(&v)

Otherwise, clarify whether ignoring duplicate state updates is intentional.

Copy link

@coderabbitai coderabbitai bot left a 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, and MaxIdleConns: 0 is 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

📥 Commits

Reviewing files that changed from the base of the PR and between 4720720 and dd88eea.

📒 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 exhaustruct and mnd warnings 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.

@capcom6 capcom6 force-pushed the actions/migrate-to-goreleaser branch from b3ce222 to e54f0eb Compare November 13, 2025 01:39
Copy link

@coderabbitai coderabbitai bot left a 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_NilContext but now uses context.Background() on lines 70 and 75 instead of a nil context. Consider renaming the test to reflect its actual behavior (e.g., TestMemoryCache_BasicOperations or TestMemoryCache_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 closing closeSignal twice to prevent panics.

Service.Close already closes every conn.closeSignal. When that happens, handleStream returns and the deferred removeConnection hits 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 a sync.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.

Close flips c.client to nil while holding mux, but Send reads and uses c.client without any synchronization. If a shutdown calls Close while Send is 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@v5 and actions/setup-go@v6, while the Go workflow (go.yml) uses actions/checkout@v4 and actions/setup-go@v5. Past review feedback indicates this inconsistency was addressed, but it remains unresolved.

Align the versions:

  • Update actions/checkout to v4 (or justify why v5 is needed here)
  • Update actions/setup-go to 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 Drain is still being ignored here. This was flagged in a previous review and remains unresolved. Inconsistent error handling could make memory leak detection results inaccurate if Drain fails.

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 < 0 still 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 strict After() comparisons since now.After(now) returns false. Items that should expire immediately will not be treated as expired.

The past review suggested using time.Now().Add(ttl) for ttl < 0 to 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:

  1. 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))
}
  1. 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.EndDate is 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 compare time.Time values 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 with fx.Annotate to apply fx.Private
Passing fx.Private as a second argument makes Fx treat it like another constructor, causing startup failures. Wrap the provider with fx.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
Defaulting cfg.HTTP.API.Path whenever 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. While 21 is 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 explicit ProcessingStatePending instead of empty string.

The empty string "" passed as the state parameter is not a valid ProcessingState enum value. Application logic should explicitly use ProcessingStatePending for 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.Second
internal/sms-gateway/handlers/root.go (1)

28-28: LGTM! Lint suppression is appropriate.

The wrapcheck suppression 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 handler
pkg/cache/memory_concurrency_test.go (1)

399-399: Consider keeping the parameter name for clarity.

While using _ *testing.T is 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 the Replace() 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.Webhook with &dto passed to BodyParserValidator) 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...), nil is correct but non-idiomatic. Since NewMemory doesn'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

📥 Commits

Reviewing files that changed from the base of the PR and between b3ce222 and e54f0eb.

📒 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.go
  • internal/sms-gateway/modules/metrics/module.go
  • internal/sms-gateway/handlers/module.go
  • internal/config/module.go
  • internal/sms-gateway/modules/auth/module.go
  • internal/sms-gateway/modules/events/module.go
  • internal/sms-gateway/modules/webhooks/module.go
  • internal/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.go
  • pkg/cache/redis.go
  • pkg/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.go
  • pkg/cache/redis.go
  • pkg/cache/memory_concurrency_test.go
  • pkg/cache/memory_bench_test.go
  • pkg/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)

@capcom6 capcom6 force-pushed the actions/migrate-to-goreleaser branch from 004675f to ebd5efe Compare November 13, 2025 06:47
Copy link

@coderabbitai coderabbitai bot left a 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, UpstreamPushRequest is 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 idGenSize is a good improvement, the error from nanoid.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. While 21 is 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, but AuthorizeUser at line 160 uses sha256(username + "\x00" + password). This mismatch means ChangePassword will 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.Path to /api whenever 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 explicit ProcessingStatePending instead of empty string.

Line 278 passes an empty string "" as the state parameter to newMessageRecipient, which is not a valid ProcessingState enum value. For type safety and clarity, explicitly use ProcessingStatePending.

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: nil and mux: 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...), nil is unconventional and less readable. Since NewMemory returns only a single value (no error), consider simplifying to:

-		pubSub, err = pubsub.NewMemory(opts...), nil
+		pubSub = pubsub.NewMemory(opts...)
+		err = nil

Or simply:

-		pubSub, err = pubsub.NewMemory(opts...), nil
+		pubSub = pubsub.NewMemory(opts...)

(The err variable will remain nil from its declaration on line 32.)

pkg/pubsub/redis.go (1)

73-74: Consider removing redundant zero-value initialization.

The explicit initialization of wg and mu with 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{}),
 	}, nil
pkg/cache/redis.go (2)

136-137: Good refactor: Named constant improves clarity.

Using itemsPerKey makes it explicit that Redis HGETALL returns key-value pairs, eliminating the magic number 2 from 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 UnusedLifetime configurable, 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 Config struct 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 just mu: sync.RWMutex{} or remove the line entirely (relying on zero-value).

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 004675f and ebd5efe.

📒 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.go
  • internal/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.go
  • pkg/cache/memory_bench_test.go
  • internal/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 %w is 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 *uint64 without 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.Errorf with the %w verb for proper error wrapping, allowing the error chain to be inspected with errors.Is or errors.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 %w and a descriptive error message.

Makefile (5)

2-3: Well-organized registry and image variables for GoReleaser integration.

The addition of registry_name and image_name centralizes Docker registry configuration, making it easier to maintain and align with the GoReleaser-based build pipeline.


10-13: Proper multiline .PHONY declaration.

The .PHONY list 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 coverage target now depends on test, ensuring test runs before coverage report generation. This separation is cleaner and more maintainable.


39-44: Clean and help targets well-implemented.

The clean target now removes coverage artifacts and dist directory (GoReleaser output), and the new help target provides user-friendly target documentation. Both implementations are professional and follow Makefile conventions.


36-37: Goreleaser DOCKER_REGISTRY integration is properly configured.

The .goreleaser.yaml correctly references the DOCKER_REGISTRY environment variable passed from the Makefile's release target. 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.User with 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 by NewBasic and NewCode middlewares.


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 ErrInvalidResponse sentinel error follows Go best practices and enables callers to check for specific error conditions using errors.Is().


50-94: LGTM! Excellent error handling improvements.

The error handling is well-structured:

  • Proper error wrapping with %w throughout
  • mapErrors helper provides consistent error propagation for batch failures
  • Sentinel error ErrInvalidResponse is appropriately used with context

Also 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 fmt import 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 Event and Retries fields are properly exported (capitalized) to enable JSON marshaling/unmarshaling via the struct tags. This is the correct approach for the serialize() and deserialize() methods.


29-36: LGTM: Improved error handling with proper wrapping.

The error wrapping using fmt.Errorf with %w provides 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.3 from refs/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: 0 for full history (needed for changelog generation)
  • Authenticates to GHCR via docker/login-action
  • Sets up Docker Buildx and Go dependencies
  • Passes GITHUB_TOKEN and DOCKER_REGISTRY to GoReleaser
  • Uses action version ~> v2 (pinned to major version)

49-114: Deploy jobs correctly depend on goreleaser.

Both deploy (line 56) and deploy-secondary (line 99) now depend on the goreleaser job, establishing the correct execution order. The deploy job extracts the version via APP_VERSION and 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_ID environment 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_ID as 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 ErrInvalidScheme as an exported sentinel error enables callers to perform typed error checking and is consistent with error handling patterns used in other modules (e.g., ErrInvalidConfig in 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 redisPubSub to RedisPubSub makes 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 %w for error chain preservation
  • Wraps configuration errors with ErrInvalidConfig sentinel
  • 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: nil initialization 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.StatusBadRequest for 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 %w for 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 %w for proper error chain propagation.

internal/config/config.go (2)

85-86: LGTM! BufferSize field properly integrated.

The new BufferSize field is correctly added to the PubSub struct and aligns with the pubsub.Config structure 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 in internal/sms-gateway/app.go and 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 of internal/config/module.go is valid and requires no wrapping with fx.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 NewDeviceSettings instead 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 ErrInvalidConfig enables callers to use errors.Is for error classification.


37-39: LGTM: Intentional parameter ignore.

The memory cache implementation doesn't need namespacing, so ignoring the name parameter is appropriate.


53-53: LGTM: Consistent error wrapping.

Properly wraps invalid scheme errors with ErrInvalidConfig sentinel.

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 ErrLockFailed and ErrNoContent enable proper error classification with errors.Is.


10-14: LGTM: ValidationError type provides better error handling.

Replacing the string-based ErrValidation with a custom ValidationError type improves type safety while maintaining backward compatibility through the Error() method.

internal/sms-gateway/modules/devices/service.go (3)

80-107: LGTM: Well-structured device selection with fallback logic.

The GetAny method provides flexible device selection:

  • Prioritizes specific device when deviceID is provided
  • Applies activity filter when duration > 0
  • Returns random device when multiple matches exist

The use of math/rand/v2 with rand.IntN is appropriate for device selection (not cryptographic), and the //nolint:gosec is 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 success flag pattern correctly addresses the previous review concern—when writeToStream fails, handleStream returns immediately, allowing the deferred removeConnection to 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 a fiber.NewError on 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 ID to id is appropriate since this is an internal (unexported) method parameter.

Also applies to: 53-53


46-50: LGTM: Explicit error handling improves clarity.

Checking the Set result 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 zap fields 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:

  • SelectFilter and SelectOptions with 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 allocations
  • SelectPending now 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: ErrNoContent sentinel error (line 232) and cache key using msg.ExtID (line 244) are now correct
  • lo.Map and lo.MapToSlice usage reduces boilerplate while maintaining clarity
  • Error messages standardized to "failed to..." pattern
  • ExportInbox now properly returns errors for caller handling

These changes improve both correctness and maintainability.

Also applies to: 126-139, 141-174, 176-261, 263-271

@capcom6 capcom6 force-pushed the actions/migrate-to-goreleaser branch from 1ea50b8 to 671b0cc Compare November 13, 2025 23:19
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

♻️ Duplicate comments (1)
internal/sms-gateway/modules/messages/service.go (1)

237-241: Address the integer overflow risk in TTL calculation.

This critical issue was flagged in previous reviews and remains unresolved. The //nolint:gosec suppression is not justified without bounds validation.

The code multiplies a user-controlled *message.TTL (uint64) by time.Second, then casts to time.Duration (int64). A TTL value exceeding ~9,223,372,036 seconds (~292 years) will cause silent integer overflow, potentially resulting in negative or incorrect durations.

The external smsgateway.Message struct has no documented TTL constraints, and the only validation is *message.TTL > 0, which does not prevent large values.

Required fix: Add explicit bounds validation before the multiplication:

+const maxTTL = 315360000 // ~10 years in seconds
+
 func (s *Service) prepareMessage(device models.Device, message MessageIn, opts EnqueueOptions) (*Message, error) {
 	// ... existing validation ...
 	
 	validUntil := message.ValidUntil
 	if message.TTL != nil && *message.TTL > 0 {
+		if *message.TTL > maxTTL {
+			return nil, ValidationError(fmt.Sprintf("TTL exceeds maximum allowed value of %d seconds", maxTTL))
+		}
-		//nolint:gosec // not a problem
 		validUntil = anys.AsPointer(
 			time.Now().Add(time.Duration(*message.TTL) * time.Second),
 		)
 	}

Based on learnings

🧹 Nitpick comments (5)
internal/sms-gateway/modules/devices/service.go (1)

80-107: Consider aligning the return type with the existing Get method.

The implementation logic is correct, and the random selection is appropriate for this use case. However, GetAny returns *models.Device (pointer) while the existing Get method (line 74) returns models.Device (value). This inconsistency may confuse callers and create different usage patterns across the codebase.

Consider returning models.Device by value for consistency:

-func (s *Service) GetAny(userID string, deviceID string, duration time.Duration) (*models.Device, error) {
+func (s *Service) GetAny(userID string, deviceID string, duration time.Duration) (models.Device, error) {
 	filter := []SelectFilter{
 		WithUserID(userID),
 	}
 	if deviceID != "" {
 		filter = append(filter, WithID(deviceID))
 	}
 	if duration > 0 {
 		filter = append(filter, ActiveWithin(duration))
 	}
 
 	devices, err := s.devices.Select(filter...)
 	if err != nil {
-		return nil, err
+		return models.Device{}, err
 	}
 
 	if len(devices) == 0 {
-		return nil, ErrNotFound
+		return models.Device{}, ErrNotFound
 	}
 
 	if len(devices) == 1 {
-		return &devices[0], nil
+		return devices[0], nil
 	}
 
 	idx := rand.IntN(len(devices)) //nolint:gosec //not critical
 
-	return &devices[idx], nil
+	return devices[idx], nil
 }
internal/sms-gateway/modules/messages/workers.go (1)

32-32: Optional: Remove redundant mutex initialization.

The explicit initialization mux: sync.Mutex{} is unnecessary since the zero value of sync.Mutex is already valid and ready to use. You can omit this field from the struct literal.

 	queue: map[uint64]struct{}{},
-	mux:   sync.Mutex{},
internal/sms-gateway/modules/messages/models.go (2)

97-110: Reconsider the nil,nil return pattern for type mismatches.

Returning (nil, nil) when the message type doesn't match violates Go's error handling convention where a nil error indicates success. This "special meaning" approach is confusing and error-prone for callers who must remember to check both return values.

Consider these alternatives:

  • Return a sentinel error like ErrWrongContentType when the type doesn't match
  • Return (nil, nil) only for truly absent content, and an error for type mismatches
  • Add a HasTextContent() boolean method so callers can check before calling

Example refactor using a sentinel error:

+var ErrWrongContentType = errors.New("content type mismatch")
+
 func (m *Message) GetTextContent() (*TextMessageContent, error) {
 	if m.Type != MessageTypeText {
-		return nil, nil //nolint:nilnil // special meaning
+		return nil, ErrWrongContentType
 	}
 	// ... rest of function
 }

 func (m *Message) GetDataContent() (*DataMessageContent, error) {
 	if m.Type != MessageTypeData {
-		return nil, nil //nolint:nilnil // special meaning
+		return nil, ErrWrongContentType
 	}
 	// ... rest of function
 }

Also applies to: 124-137


147-155: Remove redundant zero-value field assignments.

The explicit ID: 0 and MessageID: 0 assignments are unnecessary since these are the zero values for uint64. Go automatically initializes omitted fields to their zero values.

Apply this diff:

 func newMessageRecipient(phoneNumber string, state ProcessingState, err *string) MessageRecipient {
 	return MessageRecipient{
-		ID:          0,
-		MessageID:   0,
 		PhoneNumber: phoneNumber,
 		State:       state,
 		Error:       err,
 	}
 }
internal/sms-gateway/modules/messages/service.go (1)

87-90: Consider a more idiomatic builder pattern.

The *new(SelectFilter).WithExtID(...) pattern (dereferencing a pointer to a zero-value struct) is unusual in Go. While it works, it's more idiomatic to either:

  • Use a constructor: NewSelectFilter().WithExtID(...)
  • Use a struct literal directly if the builder adds little value

If you prefer to keep the builder pattern, add a constructor:

func NewSelectFilter() *SelectFilter {
	return &SelectFilter{}
}

Then use:

-	existing, err := s.messages.Get(
-		*new(SelectFilter).WithExtID(message.ID).WithDeviceID(device.ID),
-		SelectOptions{}, //nolint:exhaustruct // not needed
-	)
+	filter := NewSelectFilter().WithExtID(message.ID).WithDeviceID(device.ID)
+	existing, err := s.messages.Get(*filter, SelectOptions{})

Also applies to: 155-156

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 6a7ca2e and 217c8c1.

⛔ Files ignored due to path filters (1)
  • go.sum is excluded by !**/*.sum
📒 Files selected for processing (8)
  • cmd/sms-gateway/main.go (2 hunks)
  • go.mod (0 hunks)
  • internal/sms-gateway/modules/devices/service.go (6 hunks)
  • internal/sms-gateway/modules/messages/models.go (7 hunks)
  • internal/sms-gateway/modules/messages/service.go (7 hunks)
  • internal/sms-gateway/modules/messages/workers.go (5 hunks)
  • internal/sms-gateway/pubsub/module.go (2 hunks)
  • internal/version/version.go (1 hunks)
💤 Files with no reviewable changes (1)
  • go.mod
✅ Files skipped from review due to trivial changes (1)
  • internal/version/version.go
🚧 Files skipped from review as they are similar to previous changes (1)
  • internal/sms-gateway/pubsub/module.go
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2025-07-22T00:31:28.796Z
Learnt from: capcom6
Repo: android-sms-gateway/server PR: 154
File: internal/sms-gateway/modules/sse/service.go:67-79
Timestamp: 2025-07-22T00:31:28.796Z
Learning: In the SSE service Send method (internal/sms-gateway/modules/sse/service.go), blocking on full channels is intentional and acceptable. Timeout control is handled at the calling code level through context, not within the Send method itself. The user prefers this approach over adding a default case that would drop messages when channels are full.

Applied to files:

  • internal/sms-gateway/modules/messages/service.go
🧬 Code graph analysis (3)
internal/sms-gateway/modules/messages/models.go (1)
internal/sms-gateway/models/models.go (1)
  • SoftDeletableModel (12-16)
internal/sms-gateway/modules/devices/service.go (3)
internal/sms-gateway/models/models.go (1)
  • Device (34-45)
internal/sms-gateway/modules/devices/repository_filter.go (4)
  • SelectFilter (9-9)
  • WithUserID (23-27)
  • WithID (11-15)
  • ActiveWithin (29-33)
internal/sms-gateway/modules/devices/repository.go (1)
  • ErrNotFound (14-14)
internal/sms-gateway/modules/messages/service.go (4)
internal/sms-gateway/modules/messages/repository_filter.go (3)
  • Order (7-7)
  • SelectFilter (16-23)
  • SelectOptions (51-62)
internal/sms-gateway/modules/messages/models.go (6)
  • MessageState (157-162)
  • ProcessingState (13-13)
  • ProcessingStatePending (17-17)
  • MessageRecipient (139-145)
  • Message (36-56)
  • NewMessage (58-83)
internal/sms-gateway/modules/events/events.go (1)
  • NewMessageEnqueuedEvent (9-11)
internal/sms-gateway/modules/messages/errors.go (2)
  • ErrNoContent (7-7)
  • ValidationError (10-10)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (5)
  • GitHub Check: Benchmark
  • GitHub Check: E2E
  • GitHub Check: Lint
  • GitHub Check: Test
  • GitHub Check: Analyze (go)
🔇 Additional comments (13)
cmd/sms-gateway/main.go (2)

10-12: LGTM! Good refactor to eliminate magic strings.

The introduction of the cmdWorker constant improves code maintainability by replacing the hard-coded "worker" string throughout the file.


47-51: LGTM! Constant usage is consistent.

All references to the "worker" command string have been correctly replaced with the cmdWorker constant. The logic remains unchanged and correct.

internal/sms-gateway/modules/devices/service.go (5)

7-7: LGTM!

The math/rand/v2 import is necessary for the random device selection in the new GetAny method.


121-122: LGTM!

The refactored error handling and updated log message improve consistency across the codebase.


131-131: LGTM!

The updated log message aligns with the consistent "failed to" pattern used throughout the service.


156-156: LGTM!

The updated log message maintains consistency with the "failed to" pattern.


180-189: LGTM!

The refactored error handling with explicit variable names and consistent log messages improve code maintainability and readability.

internal/sms-gateway/modules/messages/workers.go (4)

52-52: LGTM!

Good improvement to comment formatting for consistency.


62-63: LGTM!

The migration to use the iterator-based maps.Keys with slices.AppendSeq and the built-in clear function is correct and idiomatic for Go 1.23+. The pre-allocation of the slice capacity is efficient, and the logic correctly collects all keys before clearing the queue.


73-73: LGTM!

The error message change to "failed to hash messages" is more consistent with Go conventions and improves code quality.


5-9: Go version requirement satisfied.

The project specifies Go 1.24.3 in go.mod, which exceeds the minimum requirement of Go 1.23 for the standard library maps and slices packages with iterator support used in this change.

internal/sms-gateway/modules/messages/models.go (1)

58-83: LGTM! Well-structured constructor with proper initialization.

The constructor correctly initializes Recipients with ProcessingStatePending state and sets appropriate defaults. The nolint:exhaustruct directive is justified for this partial constructor pattern.

internal/sms-gateway/modules/messages/service.go (1)

206-215: Acknowledge the intentional blocking behavior.

The asynchronous notification pattern here is appropriate for fire-and-forget event delivery. Error logging is handled correctly within the goroutine.

Based on learnings

@capcom6 capcom6 force-pushed the actions/migrate-to-goreleaser branch from 217c8c1 to e018832 Compare November 17, 2025 01:41
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
pkg/cache/memory.go (1)

81-115: Potential data race between Get and TTL‑mutating Get calls

There’s a concurrency hole in the interaction between Get, TTL‑mutating Get options, and getItem:

  • When len(opts) == 0, the getter used by Get does:

    m.mux.RLock()
    item, ok := m.items[key]
    m.mux.RUnlock()
    return item, ok

    Then getItem is called and executes item.isExpired(time.Now()) outside the mutex.

  • When len(opts) > 0 and options include TTL‑related flags (e.g. WithValidUntil, WithTTL, UpdateTTL, WithDefaultTTL), the options path does:

    m.mux.Lock()
    item, ok := m.items[key]
    if ok && !item.isExpired(time.Now()) {
        // mutate item.validUntil based on options
        item.validUntil = ...
    }
    m.mux.Unlock()

This means one goroutine can be writing item.validUntil under m.mux.Lock() while another goroutine concurrently reads item.validUntil in item.isExpired(...) without holding the mutex at all. That’s a data race on memoryItem.validUntil.

Suggested fix (one of these patterns):

  • Simplest: make the no‑options path also hold the mutex for the entire lifetime of item up to the isExpired check, by moving the expiration check inside the mutex or by having the getter perform the expiration check while holding an RLock and return a copy of the value instead of the pointer; or
  • Ensure that once an item is inserted, validUntil is never mutated (i.e. TTL‑related Get options return derived behavior without changing the underlying memoryItem), so all accesses are read‑only and safe under the current locking.

As it stands, concurrent callers mixing plain Get and TTL‑modifying Get options on the same key can trigger the race detector.

Also applies to: 146-156, 158-179

♻️ Duplicate comments (8)
internal/sms-gateway/modules/auth/service.go (1)

53-54: Add comment explaining the ignored error.

A past review comment suggested documenting why the error from nanoid.Standard(idLen) is safely ignored. While marked as addressed, the explanatory comment is still missing.

Apply this diff:

 	const idLen = 21
+	// nanoid.Standard with size 21 never returns an error
 	idgen, _ := nanoid.Standard(idLen)
internal/sms-gateway/modules/messages/service.go (6)

201-205: Nice fix on cache key + async notification

Using msg.ExtID as the cache key when setting the initial state aligns Enqueue with GetState and avoids the previous issue of caching under the wrong identifier. The async notify goroutine with captured userID/deviceID is straightforward and keeps enqueue latency low while still logging failures.

Also applies to: 206-217


243-252: prepareMessage constructor + content validation are well factored

Centralizing message construction through NewMessage and then gating content via the switch (text vs data vs ErrNoContent) makes the enqueue path much clearer. Using wrapped errors for phone normalization and content setting should also make upstream error handling and logging easier.

Also applies to: 254-265, 271-271


284-285: Using newMessageRecipient in recipientsStateToModel keeps things DRY

The refactor to build MessageRecipient via newMessageRecipient and convert state through ProcessingState(v.State) keeps recipient construction consistent with other paths and ensures any future defaults live in one place.

Also applies to: 286-309


344-345: ValidationError usage in phone cleaning improves error semantics

Returning ValidationError instances for parse failures, invalid numbers, and non‑mobile types gives callers a clear way to distinguish validation issues from other internal errors, and avoids dynamic errors.New strings. This aligns well with the dedicated validation error type introduced in this package.

Also applies to: 347-349, 351-354


176-199: Avoid returning a non‑nil state when insert fails

Enqueue currently returns a populated *MessageStateOut together with a non‑nil error when Insert fails. This breaks the usual Go convention of “on error, other return values are undefined/zero” and makes it easy for callers to accidentally use state without checking err first (this pattern was already called out previously).

Consider returning a nil state on insert failure instead:

-	state := &MessageStateOut{
+	state := &MessageStateOut{
 		DeviceID: device.ID,
 		MessageStateIn: MessageStateIn{
 			ID:    msg.ExtID,
 			State: ProcessingStatePending,
 			Recipients: lo.Map(
 				msg.Recipients,
 				func(item MessageRecipient, _ int) smsgateway.RecipientState { return modelToRecipientState(item) },
 			),
 			States: map[string]time.Time{},
 		},
 		IsHashed:    false,
 		IsEncrypted: msg.IsEncrypted,
 	}
 
 	if insErr := s.messages.Insert(msg); insErr != nil {
-		return state, insErr
+		return nil, insErr
 	}

220-233: Add upper bound validation for TTL to avoid time.Duration overflow

The TTL handling still multiplies a user‑controlled *message.TTL by time.Second after casting to time.Duration and suppresses gosec with //nolint:gosec. Since time.Duration is an int64, sufficiently large TTLs will silently overflow and produce incorrect validUntil values. This was flagged earlier and remains unfixed.

Introduce an explicit maximum TTL (in seconds), validate against it, and drop the nolint once overflow is impossible, e.g.:

-	validUntil := message.ValidUntil
-	if message.TTL != nil && *message.TTL > 0 {
-		//nolint:gosec // not a problem
-		validUntil = anys.AsPointer(
-			time.Now().Add(time.Duration(*message.TTL) * time.Second),
-		)
-	}
+	validUntil := message.ValidUntil
+	if message.TTL != nil && *message.TTL > 0 {
+		const maxTTLSeconds = uint64(10 * 365 * 24 * 60 * 60) // ~10 years
+		if *message.TTL > maxTTLSeconds {
+			return nil, fmt.Errorf("TTL exceeds maximum allowed value (%d seconds)", maxTTLSeconds)
+		}
+
+		validUntil = anys.AsPointer(
+			time.Now().Add(time.Duration(*message.TTL) * time.Second),
+		)
+	}
In Go, confirm that time.Duration is a signed int64 and that converting a large uint64 TTL in seconds via time.Duration(ttl)*time.Second can overflow, and check recommended patterns for bounding user-controlled durations.

Also applies to: 235-241

pkg/cache/memory_concurrency_test.go (1)

32-32: All for ... range loops over int variables are invalid and break compilation

Every loop of the form:

  • for range numGoroutines {
  • for i := range numGoroutines {
  • for j := range numKeys / numGoroutines {
  • for range numReaders {}, for range numWriters {}, etc.
  • for i := range numGoroutines * attemptsPerGoroutine {
  • for range attemptsPerGoroutine {}
  • and similar with numItems, numOperations / X, etc.

is illegal in Go: range operates on arrays, slices, maps, strings, or channels, not on plain int values. As written, this file will not compile.

Please rewrite all of these loops to use a conventional counter or to range over a sized slice, for example:

// Fixed patterns
for i := 0; i < numGoroutines; i++ {
    ...
}

for j := 0; j < numKeys/numGoroutines; j++ {
    ...
}

for i := 0; i < numGoroutines*attemptsPerGoroutine; i++ {
    ...
}

// Or, if you really want range:
for i := range make([]struct{}, numGoroutines) {
    ...
}

Make sure you apply this consistently in:

  • TestMemoryCache_ConcurrentReads
  • TestMemoryCache_ConcurrentWrites
  • TestMemoryCache_ConcurrentReadWrite
  • TestMemoryCache_ConcurrentSetAndGetAndDelete
  • TestMemoryCache_ConcurrentSetOrFail
  • TestMemoryCache_ConcurrentDrain
  • TestMemoryCache_ConcurrentCleanup
  • TestMemoryCache_ConcurrentGetAndDelete
  • TestMemoryCache_RaceConditionDetection

The earlier bot comment already flagged the race-test loops; this extends the same fix to all similar loops.

Also applies to: 37-39, 61-61, 66-66, 81-82, 160-160, 165-165, 360-360, 371-371, 376-376, 390-390, 399-399, 412-412, 110-110, 115-115, 128-128, 133-133, 214-214, 219-219, 256-256, 267-267, 295-295, 312-312, 327-327, 342-342

🧹 Nitpick comments (7)
internal/sms-gateway/pubsub/module.go (1)

20-22: Consider removing the no-op OnStart hook.

The OnStart hook currently does nothing and returns nil. If there's no initialization needed for the pubsub component, this hook can be safely omitted—fx.Hook only requires the lifecycle methods you actually need.

Apply this diff if the no-op OnStart is not needed for consistency across modules:

 		fx.Invoke(func(ps PubSub, logger *zap.Logger, lc fx.Lifecycle) {
 			lc.Append(fx.Hook{
-				OnStart: func(_ context.Context) error {
-					return nil
-				},
 				OnStop: func(_ context.Context) error {
cmd/sms-gateway/main.go (1)

10-12: Consider adding a constant for the "start" command for consistency.

The introduction of cmdWorker is good practice, eliminating the magic string "worker". However, line 46 still uses the magic string "start". For consistency and maintainability, consider adding a constant for it as well.

Apply this diff:

 const (
 	cmdWorker = "worker"
+	cmdStart  = "start"
 )

Then update line 46:

-	cmd := "start"
+	cmd := cmdStart
internal/sms-gateway/handlers/messages/3rdparty.go (1)

72-175: Consider refactoring to reduce function length.

The function exceeds 100 lines (102 total), which violates the linting threshold. While the logic is clear and well-structured, extracting device selection (lines 83-100) or message preparation (lines 102-131) into helper methods could improve maintainability.

internal/sms-gateway/modules/auth/service.go (1)

236-239: Consider using the context parameter or removing it.

The clean method accepts a context.Context parameter but doesn't use it. If future cleanup operations won't need the context, consider removing the parameter to simplify the signature.

Apply this diff if the context won't be needed:

-func (s *Service) clean(_ context.Context) {
+func (s *Service) clean() {
 	s.codesCache.Cleanup()
 	s.usersCache.Cleanup()
 }

And update the call site:

-			s.clean(ctx)
+			s.clean()
internal/sms-gateway/modules/messages/models.go (2)

58-82: Consider reusing newMessageRecipient in NewMessage

The constructor is clear, but the recipient initialization duplicates the defaults encoded in newMessageRecipient. Reusing the helper would keep recipient initialization consistent if new fields/defaults are added later.

-		Recipients: lo.Map(phoneNumbers, func(item string, _ int) MessageRecipient {
-			return MessageRecipient{PhoneNumber: item, State: ProcessingStatePending}
-		}),
+		Recipients: lo.Map(
+			phoneNumbers,
+			func(item string, _ int) MessageRecipient {
+				return newMessageRecipient(item, ProcessingStatePending, nil)
+			},
+		),

85-137: Document the (nil, nil) semantics in content getters

GetTextContent/GetDataContent returning (nil, nil) when Type doesn’t match is a bit non‑idiomatic and easy to misinterpret as “empty content” instead of “wrong message type”. Consider adding brief doc comments on these methods (or a package‑level comment) clarifying that (nil, nil) means “not this content type”, and reserve non‑nil errors for real failures (JSON issues).

internal/sms-gateway/modules/messages/repository.go (1)

30-101: Wrap count errors for consistency with Find errors

query.Count(&total) currently returns the bare error while Find errors are wrapped with context. For easier debugging and log searching, consider wrapping the count error similarly:

-	if err := query.Count(&total).Error; err != nil {
-		return nil, 0, err
-	}
+	if err := query.Count(&total).Error; err != nil {
+		return nil, 0, fmt.Errorf("failed to count messages: %w", err)
+	}
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 217c8c1 and e018832.

⛔ Files ignored due to path filters (1)
  • go.sum is excluded by !**/*.sum
📒 Files selected for processing (17)
  • cmd/sms-gateway/main.go (2 hunks)
  • go.mod (0 hunks)
  • internal/sms-gateway/handlers/messages/3rdparty.go (8 hunks)
  • internal/sms-gateway/modules/auth/cache.go (1 hunks)
  • internal/sms-gateway/modules/auth/service.go (7 hunks)
  • internal/sms-gateway/modules/devices/service.go (6 hunks)
  • internal/sms-gateway/modules/messages/models.go (7 hunks)
  • internal/sms-gateway/modules/messages/repository.go (6 hunks)
  • internal/sms-gateway/modules/messages/service.go (7 hunks)
  • internal/sms-gateway/modules/messages/workers.go (5 hunks)
  • internal/sms-gateway/online/metrics.go (5 hunks)
  • internal/sms-gateway/online/service.go (5 hunks)
  • internal/sms-gateway/pubsub/module.go (2 hunks)
  • internal/version/version.go (1 hunks)
  • internal/worker/locker/mysql.go (4 hunks)
  • pkg/cache/memory.go (12 hunks)
  • pkg/cache/memory_concurrency_test.go (14 hunks)
💤 Files with no reviewable changes (1)
  • go.mod
🚧 Files skipped from review as they are similar to previous changes (5)
  • internal/sms-gateway/online/metrics.go
  • internal/sms-gateway/modules/messages/workers.go
  • internal/sms-gateway/modules/devices/service.go
  • internal/sms-gateway/modules/auth/cache.go
  • internal/sms-gateway/online/service.go
🧰 Additional context used
🧠 Learnings (4)
📚 Learning: 2025-09-10T23:45:45.502Z
Learnt from: capcom6
Repo: android-sms-gateway/server PR: 178
File: internal/sms-gateway/online/module.go:14-16
Timestamp: 2025-09-10T23:45:45.502Z
Learning: fx.Decorate in uber-go/fx creates module-scoped decorated dependencies, not global modifications. Using fx.Decorate(func(log *zap.Logger) *zap.Logger { return log.Named("module_name") }) is a correct pattern for creating module-specific loggers that are scoped only to that module's dependency graph.

Applied to files:

  • internal/sms-gateway/pubsub/module.go
📚 Learning: 2025-07-22T00:31:28.796Z
Learnt from: capcom6
Repo: android-sms-gateway/server PR: 154
File: internal/sms-gateway/modules/sse/service.go:67-79
Timestamp: 2025-07-22T00:31:28.796Z
Learning: In the SSE service Send method (internal/sms-gateway/modules/sse/service.go), blocking on full channels is intentional and acceptable. Timeout control is handled at the calling code level through context, not within the Send method itself. The user prefers this approach over adding a default case that would drop messages when channels are full.

Applied to files:

  • internal/sms-gateway/modules/messages/service.go
📚 Learning: 2025-09-10T11:11:43.462Z
Learnt from: capcom6
Repo: android-sms-gateway/server PR: 178
File: pkg/cache/memory.go:59-74
Timestamp: 2025-09-10T11:11:43.462Z
Learning: In pkg/cache/memory.go, the cleanup method removes expired items first, then executes the provided callback function. This means methods like Drain that use cleanup don't need additional expiration filtering since expired items are already removed before the callback runs.

Applied to files:

  • pkg/cache/memory.go
  • pkg/cache/memory_concurrency_test.go
📚 Learning: 2025-09-10T23:53:42.006Z
Learnt from: capcom6
Repo: android-sms-gateway/server PR: 178
File: pkg/cache/redis.go:63-75
Timestamp: 2025-09-10T23:53:42.006Z
Learning: In pkg/cache/redis.go, the Redis cache implementation uses Redis's native field-level TTL via HExpireAt, meaning expired fields are automatically removed by Redis itself. This differs from the memory implementation where the application tracks expiration times. As a result, operations like Drain that call HGetAll will only return non-expired items since expired fields are already removed by Redis.

Applied to files:

  • pkg/cache/memory.go
🧬 Code graph analysis (8)
internal/worker/locker/mysql.go (1)
internal/worker/locker/locker.go (1)
  • ErrLockNotAcquired (9-9)
internal/sms-gateway/modules/messages/models.go (1)
internal/sms-gateway/models/models.go (1)
  • SoftDeletableModel (12-16)
internal/sms-gateway/handlers/messages/3rdparty.go (7)
internal/sms-gateway/handlers/devices/3rdparty.go (2)
  • NewThirdPartyController (24-36)
  • ThirdPartyController (18-22)
internal/sms-gateway/handlers/logs/3rdparty.go (2)
  • NewThirdPartyController (24-31)
  • ThirdPartyController (20-22)
internal/sms-gateway/handlers/webhooks/3rdparty.go (2)
  • NewThirdPartyController (32-40)
  • ThirdPartyController (26-30)
internal/sms-gateway/handlers/base/handler.go (1)
  • Handler (15-18)
internal/sms-gateway/modules/devices/repository.go (1)
  • ErrNotFound (14-14)
internal/sms-gateway/modules/messages/service.go (1)
  • EnqueueOptions (22-24)
internal/sms-gateway/modules/messages/repository.go (1)
  • ErrMessageAlreadyExists (17-17)
internal/sms-gateway/modules/messages/service.go (5)
internal/sms-gateway/modules/messages/repository_filter.go (3)
  • Order (7-7)
  • SelectFilter (16-23)
  • SelectOptions (51-62)
internal/sms-gateway/modules/messages/models.go (6)
  • MessageState (157-162)
  • ProcessingState (13-13)
  • ProcessingStatePending (17-17)
  • MessageRecipient (139-145)
  • Message (36-56)
  • NewMessage (58-83)
internal/sms-gateway/modules/messages/repository.go (1)
  • ErrMessageNotFound (16-16)
internal/sms-gateway/modules/events/events.go (1)
  • NewMessageEnqueuedEvent (9-11)
internal/sms-gateway/modules/messages/errors.go (2)
  • ErrNoContent (7-7)
  • ValidationError (10-10)
internal/sms-gateway/modules/messages/repository.go (3)
internal/sms-gateway/modules/messages/repository_filter.go (3)
  • SelectFilter (16-23)
  • SelectOptions (51-62)
  • Order (7-7)
internal/sms-gateway/modules/messages/models.go (3)
  • Message (36-56)
  • ProcessingStatePending (17-17)
  • MessageRecipient (139-145)
internal/sms-gateway/modules/webhooks/repository_filter.go (1)
  • WithDeviceID (41-46)
pkg/cache/memory.go (2)
pkg/cache/options.go (3)
  • GetOption (49-49)
  • AndDelete (89-93)
  • Option (6-6)
pkg/cache/cache.go (1)
  • Cache (5-40)
pkg/cache/memory_concurrency_test.go (1)
pkg/cache/errors.go (2)
  • ErrKeyNotFound (9-9)
  • ErrKeyExists (13-13)
internal/sms-gateway/modules/auth/service.go (5)
internal/sms-gateway/online/service.go (2)
  • New (28-37)
  • Service (14-17)
internal/sms-gateway/modules/auth/types.go (1)
  • OneTimeCode (15-18)
internal/sms-gateway/models/models.go (4)
  • User (18-24)
  • NewUser (26-32)
  • Device (34-45)
  • NewDevice (47-53)
pkg/crypto/passwords.go (2)
  • MakeBCryptHash (14-20)
  • CompareBCryptHash (22-27)
internal/sms-gateway/modules/auth/errors.go (1)
  • ErrAuthorizationFailed (6-6)
🪛 GitHub Check: Lint
internal/sms-gateway/handlers/messages/3rdparty.go

[failure] 72-72:
Function 'post' is too long (102 > 100) (funlen)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
  • GitHub Check: Benchmark
  • GitHub Check: E2E
  • GitHub Check: Analyze (go)
🔇 Additional comments (33)
internal/sms-gateway/pubsub/module.go (1)

5-5: LGTM: Error wrapping improves context.

The addition of error wrapping with fmt.Errorf provides better context when the pubsub close operation fails, and the %w verb properly preserves the error chain for inspection with errors.Is and errors.As.

Also applies to: 26-26

cmd/sms-gateway/main.go (1)

47-51: LGTM!

The refactoring correctly replaces magic strings with the cmdWorker constant while preserving the original logic. The command dispatch works as expected: if the first argument is "worker", run the worker; otherwise, run the SMS gateway server.

internal/worker/locker/mysql.go (2)

45-47: LGTM! Improved error variable naming.

The introduction of the lockErr variable improves code clarity by using a more descriptive name and avoiding reuse of the generic err variable. The error handling logic remains correct and maintains proper error wrapping.


74-74: LGTM! Enhanced error context.

The enhanced error wrapping at both locations improves debugging by providing additional context while correctly preserving error unwrapping with %w. Line 74 includes the key for which no connection was held, and Line 85 clarifies that the lock was not held or doesn't exist.

Also applies to: 85-85

internal/sms-gateway/handlers/messages/3rdparty.go (2)

43-52: LGTM! Constructor follows consistent pattern.

The constructor properly initializes the controller with dependency injection and matches the pattern used in other handlers throughout the codebase.


195-211: LGTM! Error handling is comprehensive and consistent.

The handlers properly validate inputs, log errors with relevant context (user_id, device_id, etc.), and return appropriate HTTP status codes. The error handling pattern is well-structured across all endpoints.

Also applies to: 226-240, 256-278

internal/sms-gateway/modules/auth/service.go (11)

69-97: LGTM! Clean refactor with proper error handling.

The code generation logic is correct:

  • 3 random bytes produce 0-16777215 via bit shifting
  • Modulo 1000000 yields 0-999999
  • Formatting as %06d ensures 6-digit codes

The OneTimeCode return type and structured error handling are good improvements.


99-111: LGTM! Good separation of concerns.

Delegating password hashing to the crypto package and using the models.NewUser constructor improves modularity and consistency.


113-121: LGTM! Signature now consistent with the API.

The method signature correctly accepts *models.User pointer, addressing the previous review feedback. The use of models.NewDevice constructor and pointer return type maintains consistency across the service API.


127-137: LGTM! Consistent error handling.

Using the sentinel ErrAuthorizationFailed error improves consistency and enables better error checking by callers.


139-155: LGTM! Improved error wrapping and readability.

Wrapping errors with ErrAuthorizationFailed maintains consistency, and the named timeout constant improves code clarity.


157-176: LGTM! Robust caching with improved error handling.

The refactored caching approach with the custom usersCache properly addresses the cache key collision concern from previous reviews. Logging cache set failures without failing authentication is the right trade-off between reliability and observability.


178-191: LGTM! Consistent with pointer-based API.

The pointer return type and descriptive error messages maintain consistency across the service.


193-218: LGTM! Improved readability and error handling.

Named error variables and proper cache invalidation with error logging enhance code clarity and maintainability.


64-64: codeTTL constant is properly defined.

The codeTTL constant is correctly defined in internal/sms-gateway/modules/auth/types.go as const codeTTL = 5 * time.Minute, ensuring the cache initialization at line 64 is valid.


222-234: No issues found.

The Run method is properly integrated into the application lifecycle at internal/sms-gateway/modules/auth/module.go:22 where it's invoked with go svc.Run(ctx). The implementation correctly handles context cancellation and periodic cleanup.


42-42: Verify thread safety of the custom usersCache wrapper.

The usersCache wrapper (internal/sms-gateway/modules/auth/cache.go) delegates directly to an external package github.com/capcom6/go-helpers/cache with no additional synchronization mechanisms. While the wrapper methods have no explicit sync primitives (Mutex, RWMutex, etc.), thread safety depends entirely on whether the underlying cache.Cache[models.User] provides thread-safe operations by design.

Action required: Verify the thread safety guarantees of the external github.com/capcom6/go-helpers/cache package, or add synchronization (e.g., RWMutex) to the wrapper if the underlying cache is not thread-safe for concurrent access patterns in AuthorizeUser and ChangePassword.

internal/sms-gateway/modules/messages/models.go (2)

37-56: Embedding + migration wiring looks consistent

Embedding SoftDeletableModel into Message and migrating using db.AutoMigrate(new(Message), new(MessageRecipient), new(MessageState)) is coherent and keeps the schema definition centralized; the additional error wrapping on migration is also appropriate.

Also applies to: 165-169


147-155: Helper for MessageRecipient initialization is tidy

newMessageRecipient cleanly centralizes the zero‑ID, zero‑MessageID and optional error defaults for new recipients, which makes the call sites in the service easier to reason about.

internal/sms-gateway/modules/messages/repository.go (4)

103-111: SelectPending delegation via filter/options looks fine

Building a SelectFilter/SelectOptions and delegating to Select keeps the pending‑selection logic centralized; the maxPendingBatch limit is also explicitly enforced here. The *new(T).With... pattern is a bit unusual but readable and doesn’t introduce functional issues.


112-127: Get reusing Select simplifies the query surface

Having Get call Select and then enforce the 0/1/many invariants keeps all filter/option handling in one place, while still returning the sentinel ErrMessageNotFound and ErrMultipleMessagesFound as before. This looks correct.


143-174: Transactional UpdateState is a solid improvement

Wrapping the state update, state‑history upsert, and recipients update in a single transaction ensures they succeed or fail atomically, and the final error wrapping around UpdateState gives better context to callers. The use of Message.ID + PhoneNumber as the recipient key is consistent with the schema.


195-201: Cleanup delete shape matches the refactored model usage

Using Delete((*Message)(nil)) keeps the model/table reference consistent with Select and still scopes deletion correctly to non‑pending messages older than until. No issues here.

internal/sms-gateway/modules/messages/service.go (5)

73-84: SelectPending wiring from service to repository looks good

Defaulting an empty order to MessagesOrderLIFO and then delegating to Repository.SelectPending, followed by slices.MapOrError(messages, messageToDomain), cleanly separates persistence from DTO mapping. The nolint on wrapcheck is justified given underlying errors are already wrapped.


87-90: UpdateState mapping and caching flow are coherent

Fetching the existing message via SelectFilter (ExtID + DeviceID), rebuilding States via lo.MapToSlice, and using recipientsStateToModel to normalize recipient updates all look correct. Updating the DB first and then refreshing the cache with existing.ExtID keeps the cache in sync with the persisted state.

Also applies to: 100-111, 113-115, 117-119


126-130: SelectStates now correctly reuses the generic Select API

Mutating filter.UserID and then calling s.messages.Select before mapping to MessageStateOut gives you one canonical query path, and the new "failed to select messages" wrapping makes logs clearer. The revised signature that returns both []MessageStateOut and total is straightforward.

Also applies to: 135-139


141-143: GetState cache and DB fallback logic looks sound

The cache‑then‑DB pattern with a nil‑sentinel for “not found” is clear, and using user.ID + id consistently as the key avoids the earlier mismatch problems. The updated code also ensures not‑found DB results are cached as nil to prevent repeated lookups.

Also applies to: 155-156, 160-162, 168-171


277-282: ExportInbox now wraps notify errors with context

Wrapping eventsSvc.Notify failures with "failed to notify device" provides useful context while keeping the API simple. Returning nil on success remains straightforward.

pkg/cache/memory_concurrency_test.go (2)

5-5: Use of errors.Is and errors import looks good

Importing errors and switching tests to use errors.Is against cache.ErrKeyNotFound / cache.ErrKeyExists makes the tests resilient to wrapped errors and matches how the cache API now reports errors. No issues here.

Also applies to: 118-118, 221-228, 284-287, 298-299, 345-346, 380-381, 393-394


246-252: Concurrent Drain and Cleanup expectations align with cache behavior

The concurrent Drain and Cleanup tests exercise the semantics that:

  • Drain returns only non-expired items and clears the cache, and
  • Cleanup removes expired items so subsequent Get calls see ErrKeyNotFound.

This matches how MemoryCache.cleanup first drops expired entries and then runs the callback/Drain logic, and validates the behavior under concurrent calls. No changes needed here.

Also applies to: 272-277, 283-292, 295-301, 304-349

pkg/cache/memory.go (3)

9-23: Exported MemoryCache type + constructor/interface assertion look consistent

Renaming the concrete type to MemoryCache, returning *MemoryCache from NewMemory, and asserting var _ Cache = (*MemoryCache)(nil) keeps the public surface coherent with the Cache interface and avoids ambiguous unexported types. The initialization of the internal map and mutex looks correct.

Also applies to: 195-199


64-78: Drain + cleanup correctly filter expired items before draining

Drain delegating to m.cleanup and then snapshotting m.items:

  • Ensures expired entries are removed before the callback runs.
  • Moves the (already-cleaned) map into a local cpy and replaces m.items with a fresh map while holding the lock.
  • Iterates over cpy after unlocking, which is safe because no other goroutine retains a reference to the old map.

This matches the intended semantics that Drain returns only non-expired items and leaves the cache empty afterwards, consistent with the cache interface and prior learnings about cleanup.

Also applies to: 181-193


123-144: Set / SetOrFail semantics align with Cache interface

Set always overwrites the key with a new memoryItem built through m.newItem, and SetOrFail:

  • Rejects non‑expired existing entries with ErrKeyExists.
  • Treats expired entries as available and overwrites them with a fresh item.

Both operations are protected by m.mux and match the documented Cache behavior. No changes needed here.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

♻️ Duplicate comments (2)
internal/sms-gateway/handlers/messages/3rdparty.go (2)

230-231: Capitalize error message for consistency.

The error message starts with lowercase "failed" while other error messages in this file use title case (e.g., line 112: "No message content provided", line 199: "Failed to retrieve message history").

Apply this diff:

-		return fiber.NewError(fiber.StatusInternalServerError, "failed to get message state")
+		return fiber.NewError(fiber.StatusInternalServerError, "Failed to get message state")

263-269: Capitalize error messages for consistency.

The error messages on lines 264 and 269 start with lowercase letters, inconsistent with the capitalized error messages used elsewhere in this file.

Apply this diff:

-		return fiber.NewError(fiber.StatusInternalServerError, "failed to get device")
+		return fiber.NewError(fiber.StatusInternalServerError, "Failed to get device")
-		return fiber.NewError(fiber.StatusInternalServerError, "failed to export inbox")
+		return fiber.NewError(fiber.StatusInternalServerError, "Failed to export inbox")
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between e018832 and 7db6313.

📒 Files selected for processing (2)
  • internal/sms-gateway/handlers/messages/3rdparty.go (8 hunks)
  • internal/sms-gateway/modules/messages/errors.go (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
internal/sms-gateway/handlers/messages/3rdparty.go (6)
internal/sms-gateway/handlers/base/handler.go (1)
  • Handler (15-18)
internal/sms-gateway/modules/messages/service.go (1)
  • EnqueueOptions (22-24)
internal/sms-gateway/modules/messages/errors.go (2)
  • ValidationError (9-9)
  • ErrNoContent (6-6)
internal/sms-gateway/modules/messages/repository.go (3)
  • ErrMultipleMessagesFound (18-18)
  • ErrMessageNotFound (16-16)
  • ErrMessageAlreadyExists (17-17)
internal/sms-gateway/modules/devices/repository.go (3)
  • ErrNotFound (14-14)
  • ErrInvalidFilter (15-15)
  • ErrMoreThanOne (16-16)
internal/sms-gateway/modules/devices/errors.go (1)
  • ErrInvalidUser (6-6)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
  • GitHub Check: E2E
  • GitHub Check: Benchmark
  • GitHub Check: Analyze (go)
🔇 Additional comments (10)
internal/sms-gateway/modules/messages/errors.go (3)

3-3: LGTM!

The errors import is correctly added to support the sentinel error pattern.


5-7: LGTM!

The sentinel error ErrNoContent follows Go conventions and provides a clear, comparable error value.


9-13: LGTM! Improved naming convention.

The rename from ErrValidation to ValidationError better aligns with Go conventions where error types typically end with "Error" (e.g., os.PathError, json.UnmarshalTypeError), while the "Err" prefix is reserved for error variables. The implementation correctly provides a simple, descriptive validation error type.

internal/sms-gateway/handlers/messages/3rdparty.go (7)

44-53: LGTM! Clean constructor pattern.

The explicit dependency wiring through the base.Handler pattern is clear and follows the codebase conventions.


72-82: LGTM! Consistent error handling.

The parsing validation errors correctly return 400 Bad Request responses.


84-98: LGTM! Good error logging.

The device selection logic includes comprehensive error logging with relevant context (user_id, device_id). The wrapped error is properly caught by the errorHandler middleware.


130-144: Error handling addressed via centralized middleware.

The previous review concern about returning fiber errors is now addressed through the errorHandler middleware (line 275), which catches wrapped errors and maps them to appropriate HTTP status codes. The structured logging provides good error context.


150-155: LGTM! Improved structured logging.

The route URL generation failure logging is now properly structured with relevant context fields.


189-194: LGTM! Consistent parsing pattern.

The query parameter handling follows the same pattern as other handlers in this file.


250-255: LGTM! Consistent parsing pattern.

The request parsing follows the established pattern in this file.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

♻️ Duplicate comments (3)
internal/sms-gateway/handlers/messages/3rdparty.go (3)

230-231: Standardize error message capitalization.

The error message starts with a lowercase letter. This was flagged in a previous review but remains unaddressed.

-		return fiber.NewError(fiber.StatusInternalServerError, "failed to get message state")
+		return fiber.NewError(fiber.StatusInternalServerError, "Failed to get message state")

263-264: Standardize error message capitalization.

The error message starts with a lowercase letter. This was flagged in a previous review but remains unaddressed.

-		return fiber.NewError(fiber.StatusInternalServerError, "failed to get device")
+		return fiber.NewError(fiber.StatusInternalServerError, "Failed to get device")

268-269: Standardize error message capitalization.

The error message starts with a lowercase letter. This was flagged in a previous review but remains unaddressed.

-		return fiber.NewError(fiber.StatusInternalServerError, "failed to export inbox")
+		return fiber.NewError(fiber.StatusInternalServerError, "Failed to export inbox")
🧹 Nitpick comments (7)
pkg/health/service.go (1)

40-46: Minor inconsistencies in error handling.

The code has two small issues:

  • Line 40 logs "failed check" (lowercase) while line 42 sets Description: "Failed check" (capitalized). Consider standardizing the capitalization.
  • Line 43's ObservedUnit: "" is redundant since empty string is the zero value for strings in Go.

Apply this diff to improve consistency and remove redundancy:

-		s.logger.Error("failed check", zap.String("provider", p.Name()), zap.Error(err))
+		s.logger.Error("Failed check", zap.String("provider", p.Name()), zap.Error(err))
 		check.Checks[p.Name()] = CheckDetail{
 			Description:   "Failed check",
-			ObservedUnit:  "",
 			ObservedValue: 0,
 			Status:        StatusFail,
 		}
internal/sms-gateway/modules/sse/service.go (1)

46-46: Remove redundant mutex initialization.

The zero value of sync.RWMutex is ready to use. Explicit initialization with sync.RWMutex{} is unnecessary.

Apply this diff:

-		mu:          sync.RWMutex{},
+		mu:          sync.RWMutex,

Or simply omit the field from initialization—Go will zero-initialize it automatically.

internal/sms-gateway/handlers/settings/3rdparty.go (2)

85-85: Consider restoring the map capacity hint.

The previous capacity hint of 8 provided a minor optimization by pre-allocating space, reducing allocations during map growth. While not critical, restoring it could be beneficial if typical settings objects contain around 8 keys.

-	settings := make(map[string]any)
+	settings := make(map[string]any, 8)

119-119: Consider restoring the map capacity hint.

Similar to the put method, the previous capacity hint of 8 provided a minor optimization. Consider restoring it for consistency and to reduce allocations during map growth.

-	settings := make(map[string]any)
+	settings := make(map[string]any, 8)
internal/sms-gateway/modules/messages/models.go (1)

58-82: Constructor and recipient helper are consistent with GORM expectations

NewMessage plus newMessageRecipient build a Message with pending state and a recipient per phone number, leaving ID/MessageID at zero so GORM can populate them on insert. That’s a good pattern and should work correctly with Create and the defined associations.

One behavioral note: Type and Content remain unset until SetTextContent/SetDataContent are called, so callers must ensure they set content before persisting; otherwise you may rely on DB defaults or see enum issues depending on how GORM emits zero values. If that’s a concern, we could consider enforcing content-type initialization in NewMessage or adding validation before insert.

Also applies to: 147-155

internal/sms-gateway/modules/messages/repository.go (2)

30-101: Select implementation is sound; consider one small robustness tweak

The query assembly (date range, ext/user/device/state filters), count-before-pagination, ordering, and optional preloads/joins all look correct and consistent with the SelectFilter/SelectOptions types.

One minor robustness point: messages := make([]Message, 0, min(options.Limit, int(total))) assumes min behaves reasonably when options.Limit is zero (no limit) and when total is large. That’s probably fine, but if min is custom, double‑check it doesn’t do anything surprising with zero/negative values. Worst‑case, a simpler make([]Message, 0) would avoid depending on min at all.


143-174: Transactional UpdateState greatly improves consistency

Wrapping the message state update, MessageState insertion, and MessageRecipient updates in a single transaction is a solid improvement in consistency. Using Clauses(clause.OnConflict{DoNothing: true}) against the message_states unique index and updating recipients via Model((*MessageRecipient)(nil)) + Where(message_id, phone_number) is appropriate and avoids partial updates.

One assumption worth calling out: this code expects message.ID to already be set (i.e., the message has been persisted) so that v.MessageID = message.ID and the recipient update filter are meaningful. If there’s any chance UpdateState might be called on unsaved messages, it would be safer to either guard against message.ID == 0 or document that as a precondition.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 7db6313 and 92d6a34.

📒 Files selected for processing (10)
  • internal/sms-gateway/handlers/messages/3rdparty.go (8 hunks)
  • internal/sms-gateway/handlers/settings/3rdparty.go (4 hunks)
  • internal/sms-gateway/modules/devices/repository.go (5 hunks)
  • internal/sms-gateway/modules/events/module.go (1 hunks)
  • internal/sms-gateway/modules/events/service.go (7 hunks)
  • internal/sms-gateway/modules/messages/models.go (7 hunks)
  • internal/sms-gateway/modules/messages/repository.go (7 hunks)
  • internal/sms-gateway/modules/sse/service.go (8 hunks)
  • internal/version/version.go (1 hunks)
  • pkg/health/service.go (3 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
  • internal/sms-gateway/modules/devices/repository.go
  • internal/version/version.go
🧰 Additional context used
🧠 Learnings (3)
📚 Learning: 2025-09-10T23:45:45.502Z
Learnt from: capcom6
Repo: android-sms-gateway/server PR: 178
File: internal/sms-gateway/online/module.go:14-16
Timestamp: 2025-09-10T23:45:45.502Z
Learning: fx.Decorate in uber-go/fx creates module-scoped decorated dependencies, not global modifications. Using fx.Decorate(func(log *zap.Logger) *zap.Logger { return log.Named("module_name") }) is a correct pattern for creating module-specific loggers that are scoped only to that module's dependency graph.

Applied to files:

  • internal/sms-gateway/modules/events/module.go
📚 Learning: 2025-07-22T00:31:28.796Z
Learnt from: capcom6
Repo: android-sms-gateway/server PR: 154
File: internal/sms-gateway/modules/sse/service.go:67-79
Timestamp: 2025-07-22T00:31:28.796Z
Learning: In the SSE service Send method (internal/sms-gateway/modules/sse/service.go), blocking on full channels is intentional and acceptable. Timeout control is handled at the calling code level through context, not within the Send method itself. The user prefers this approach over adding a default case that would drop messages when channels are full.

Applied to files:

  • internal/sms-gateway/modules/sse/service.go
📚 Learning: 2025-07-22T00:34:31.896Z
Learnt from: capcom6
Repo: android-sms-gateway/server PR: 154
File: internal/sms-gateway/modules/sse/service.go:89-92
Timestamp: 2025-07-22T00:34:31.896Z
Learning: In Go SSE implementations, when cleaning up connections with buffered channels, avoid closing the data channels directly as this can cause panics if concurrent goroutines attempt to write to them. Instead, use a separate close signal channel to notify writers that the connection should be terminated, allowing the data channels to be garbage collected naturally when the connection is removed.

Applied to files:

  • internal/sms-gateway/modules/sse/service.go
🧬 Code graph analysis (8)
internal/sms-gateway/modules/events/module.go (5)
internal/sms-gateway/app.go (1)
  • Module (33-58)
internal/sms-gateway/modules/events/service.go (2)
  • NewService (33-52)
  • Service (20-31)
internal/sms-gateway/modules/messages/service.go (2)
  • NewService (40-63)
  • Service (26-38)
internal/sms-gateway/modules/settings/service.go (2)
  • NewService (27-35)
  • Service (19-25)
internal/sms-gateway/modules/webhooks/service.go (2)
  • NewService (39-50)
  • Service (28-37)
internal/sms-gateway/handlers/settings/3rdparty.go (2)
internal/sms-gateway/handlers/messages/3rdparty.go (2)
  • NewThirdPartyController (44-53)
  • ThirdPartyController (37-42)
internal/sms-gateway/modules/settings/models.go (1)
  • DeviceSettings (10-17)
pkg/health/service.go (1)
pkg/health/types.go (3)
  • Provider (69-75)
  • Checks (67-67)
  • CheckResult (21-24)
internal/sms-gateway/handlers/messages/3rdparty.go (6)
internal/sms-gateway/handlers/base/handler.go (1)
  • Handler (15-18)
internal/sms-gateway/modules/messages/service.go (1)
  • EnqueueOptions (22-24)
internal/sms-gateway/modules/messages/errors.go (2)
  • ValidationError (9-9)
  • ErrNoContent (6-6)
internal/sms-gateway/modules/messages/repository.go (3)
  • ErrMultipleMessagesFound (18-18)
  • ErrMessageNotFound (16-16)
  • ErrMessageAlreadyExists (17-17)
internal/sms-gateway/modules/devices/repository.go (3)
  • ErrNotFound (14-14)
  • ErrInvalidFilter (15-15)
  • ErrMoreThanOne (16-16)
internal/sms-gateway/modules/devices/errors.go (1)
  • ErrInvalidUser (6-6)
internal/sms-gateway/modules/events/service.go (5)
internal/sms-gateway/pubsub/pubsub.go (1)
  • PubSub (15-15)
internal/sms-gateway/modules/events/errors.go (1)
  • ErrValidationFailed (6-6)
internal/sms-gateway/modules/events/metrics.go (6)
  • DeliveryTypeUnknown (20-20)
  • FailureReasonPublishError (23-23)
  • EventTypeUnknown (26-26)
  • FailureReasonSerializationError (22-22)
  • DeliveryTypePush (18-18)
  • FailureReasonProviderFailed (24-24)
internal/sms-gateway/modules/events/types.go (1)
  • Event (10-13)
internal/sms-gateway/modules/sse/types.go (1)
  • Event (7-10)
internal/sms-gateway/modules/messages/models.go (1)
internal/sms-gateway/models/models.go (1)
  • SoftDeletableModel (12-16)
internal/sms-gateway/modules/messages/repository.go (2)
internal/sms-gateway/modules/messages/repository_filter.go (3)
  • SelectFilter (16-23)
  • SelectOptions (51-62)
  • Order (7-7)
internal/sms-gateway/modules/messages/models.go (3)
  • Message (36-56)
  • ProcessingStatePending (17-17)
  • MessageRecipient (139-145)
internal/sms-gateway/modules/sse/service.go (2)
internal/sms-gateway/modules/sse/errors.go (1)
  • ErrNoConnection (6-6)
internal/sms-gateway/modules/sse/metrics.go (2)
  • ErrorTypeMarshalError (22-22)
  • ErrorTypeWriteFailure (21-21)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
  • GitHub Check: E2E
  • GitHub Check: Benchmark
  • GitHub Check: Analyze (go)
🔇 Additional comments (23)
pkg/health/service.go (3)

9-21: LGTM! Clean rename from HealthProvider to Provider.

The struct field and constructor parameter have been consistently updated to use the renamed Provider type.


23-26: LGTM! Method signature updated consistently.

The probe function parameter now correctly accepts Provider instead of HealthProvider.


62-78: LGTM! Check methods updated consistently.

All three check methods (CheckReadiness, CheckLiveness, CheckStartup) have been updated to use the renamed Provider type in their closures. The logic remains unchanged.

internal/sms-gateway/modules/sse/service.go (7)

17-19: Good practice using a named constant.

Defining eventsBufferSize as a constant improves maintainability and eliminates the magic number.


54-105: LGTM - Error handling improved.

The error wrapping with %w and descriptive messages (lines 62, 69, 98) follows Go best practices. The non-blocking send with buffer-full logging (lines 84-91) aligns with the intentional design documented in learnings.


120-131: LGTM - Clean separation of concerns.

Extracting the streaming logic to handleStream improves readability and testability.


133-179: LGTM - Write failure handling correctly implemented.

The success flag pattern (lines 150-163) properly addresses the previous review concern: write errors are now captured and cause handleStream to return, triggering the deferred removeConnection cleanup. The conditional ticker pattern (lines 137-145) is idiomatic Go—when keepAlivePeriod <= 0, the nil channel effectively disables keepalives in the select.


181-192: LGTM - Proper error handling.

Both write and flush errors are properly wrapped with descriptive messages and metrics are correctly incremented.


194-218: LGTM - Correct channel setup.

Using the eventsBufferSize constant (line 202) and maintaining the separate closeSignal channel aligns with best practices documented in learnings.


220-245: LGTM - Proper cleanup.

The method correctly closes only the closeSignal channel (line 227), not the data channel, preventing potential panics as documented in learnings. Cleanup and metrics handling are correct.

internal/sms-gateway/handlers/settings/3rdparty.go (2)

35-44: LGTM!

The constructor follows the consistent pattern used across other handlers in this PR, properly wiring dependencies through explicit parameters.


56-64: LGTM!

The error message update improves consistency across the codebase by using the "failed to" pattern.

internal/sms-gateway/handlers/messages/3rdparty.go (3)

44-53: LGTM!

The constructor follows the consistent dependency injection pattern established across handlers in this PR.


72-144: LGTM!

The updated error handling pattern works well with the new centralized errorHandler middleware (lines 275-306), which maps domain-specific errors to appropriate HTTP status codes. The added logging provides better observability for debugging failures.


309-309: LGTM!

The error handler middleware is correctly registered to intercept and process errors from all routes in this router.

internal/sms-gateway/modules/messages/models.go (4)

9-9: SoftDeletableModel embedding and new lo dependency look fine

Embedding models.SoftDeletableModel into Message and adding lo for slice mapping are both reasonable and idiomatic choices here. No correctness issues spotted as long as SoftDeletableModel does not itself declare an ID field (which would conflict with Message.ID).

Also applies to: 36-56


85-110: Pointer-based text content handling and error wrapping look correct

The switch to pointer semantics in GetTextContent (using new(TextMessageContent) and returning (*TextMessageContent, error)) is safe and eliminates returning the address of a stack value. The nil, nil return on type mismatch is clearly documented with //nolint:nilnil and keeps the API simple for callers that treat “not text” as “no content”. Error wrapping in SetTextContent and GetTextContent is also fine.


112-137: Data content handling mirrors text content correctly

SetDataContent/GetDataContent mirror the text variants with pointer allocation, nil, nil semantics on type mismatch, and consistent error wrapping. The implementation looks correct and symmetric with the text-content API.


164-169: Wrapped migration error improves debuggability

Using db.AutoMigrate(new(Message), new(MessageRecipient), new(MessageState)) and wrapping any error with "messages migration failed" is a straightforward improvement; no issues here.

internal/sms-gateway/modules/messages/repository.go (4)

16-18: Error value changes are reasonable but may affect callers

Switching ErrMessageNotFound to a plain errors.New while keeping ErrMessageAlreadyExists and ErrMultipleMessagesFound as package-level sentinels is fine internally. Just ensure external callers aren’t still relying on errors.Is(err, gorm.ErrRecordNotFound) for repo methods; they should now key off ErrMessageNotFound instead.


103-110: SelectPending correctly reuses the generic Select API

Using Select with a SelectFilter pre-populated via fluent methods and SelectOptions that include recipients, a limit, and order is a clean reuse of the generic query logic. This should preserve previous semantics (pending messages for a device, ordered FIFO/LIFO) while centralizing the query code.


112-127: Get wrapper behavior is clear and consistent

Get delegating to Select, wrapping any underlying error, and translating 0/1/>1 results into ErrMessageNotFound / ErrMultipleMessagesFound is straightforward. This should give callers a simple API with deterministic error contracts.


195-201: Cleanup query and Delete model usage look correct

The cleanup routine filters out pending messages and deletes only those created before until, using Delete((*Message)(nil)). That’s aligned with the rest of the repository’s Model((*T)(nil)) pattern and should behave as intended.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

♻️ Duplicate comments (4)
internal/sms-gateway/handlers/messages/3rdparty.go (4)

220-231: Capitalize 500 message in get handler for consistency

The get handler correctly maps messages.ErrMessageNotFound to 404 and logs other failures, but the generic 500 message is still lowercase:

return fiber.NewError(fiber.StatusInternalServerError, "failed to get message state")

For consistency with other responses, update to:

-		return fiber.NewError(fiber.StatusInternalServerError, "failed to get message state")
+		return fiber.NewError(fiber.StatusInternalServerError, "Failed to get message state")

250-270: Capitalize 500 messages in postInboxExport for consistency

postInboxExport’s control flow and logging look good (special-casing devices.ErrNotFound while logging unexpected errors). The remaining nits are capitalization of the 500 messages:

return fiber.NewError(fiber.StatusInternalServerError, "failed to get device")
return fiber.NewError(fiber.StatusInternalServerError, "failed to export inbox")

To match the rest of the file:

-		return fiber.NewError(fiber.StatusInternalServerError, "failed to get device")
+		return fiber.NewError(fiber.StatusInternalServerError, "Failed to get device")
...
-		return fiber.NewError(fiber.StatusInternalServerError, "failed to export inbox")
+		return fiber.NewError(fiber.StatusInternalServerError, "Failed to export inbox")

189-199: Capitalize 500 message in list handler for consistency

The 500 path in list logs correctly but still returns a lowercase message ("failed to retrieve message history"), which is inconsistent with other user-facing errors.

Consider capitalizing it:

-		return fiber.NewError(fiber.StatusInternalServerError, "failed to retrieve message history")
+		return fiber.NewError(fiber.StatusInternalServerError, "Failed to retrieve message history")

311-312: Capitalize generic 500 error message in errorHandler

The middleware correctly logs unexpected errors and returns a 500, but the message remains lowercase:

return fiber.NewError(fiber.StatusInternalServerError, "failed to handle request")

Align with the capitalization style used elsewhere:

-	return fiber.NewError(fiber.StatusInternalServerError, "failed to handle request")
+	return fiber.NewError(fiber.StatusInternalServerError, "Failed to handle request")
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 92d6a34 and 53f7d3e.

📒 Files selected for processing (1)
  • internal/sms-gateway/handlers/messages/3rdparty.go (8 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
internal/sms-gateway/handlers/messages/3rdparty.go (6)
internal/sms-gateway/handlers/base/handler.go (1)
  • Handler (15-18)
internal/sms-gateway/modules/messages/service.go (1)
  • EnqueueOptions (22-24)
internal/sms-gateway/modules/messages/errors.go (2)
  • ValidationError (9-9)
  • ErrNoContent (6-6)
internal/sms-gateway/modules/messages/repository.go (3)
  • ErrMultipleMessagesFound (18-18)
  • ErrMessageNotFound (16-16)
  • ErrMessageAlreadyExists (17-17)
internal/sms-gateway/modules/devices/repository.go (3)
  • ErrNotFound (14-14)
  • ErrInvalidFilter (15-15)
  • ErrMoreThanOne (16-16)
internal/sms-gateway/modules/devices/errors.go (1)
  • ErrInvalidUser (6-6)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
  • GitHub Check: E2E
  • GitHub Check: Benchmark
  • GitHub Check: Analyze (go)
🔇 Additional comments (3)
internal/sms-gateway/handlers/messages/3rdparty.go (3)

44-53: Constructor wiring for ThirdPartyController looks correct

The new NewThirdPartyController correctly propagates Logger, Validator, and both services from thirdPartyControllerParams into the embedded base.Handler and struct fields. This aligns with the fx.In pattern and should integrate cleanly with DI.


72-97: post handler error flow is coherent with middleware-based handling

The post handler now consistently:

  • Uses QueryParserValidator / BodyParserValidator to return 400s via fiber.NewError.
  • Logs device-selection and enqueue failures with user/device context.
  • Wraps downstream errors and lets the centralized errorHandler map domain errors to HTTP statuses.

Given the presence of errorHandler, returning wrapped errors here is reasonable and keeps the handler logic focused on request shaping and logging.

Also applies to: 130-143, 150-155


315-316: Registering errorHandler middleware is appropriate

Using router.Use(h.errorHandler) ensures all 3rd‑party routes share centralized error-to-HTTP mapping, which simplifies individual handlers and keeps status code logic in one place.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

♻️ Duplicate comments (3)
internal/sms-gateway/handlers/messages/3rdparty.go (3)

252-270: Align postInboxExport error responses with capitalization and middleware behavior

Two minor points here:

  1. Capitalization consistency (already flagged earlier):

    • Line 264: "failed to get device"
    • Line 269: "failed to export inbox"

    Suggest:

  •   return fiber.NewError(fiber.StatusInternalServerError, "failed to get device")
    
  •   return fiber.NewError(fiber.StatusInternalServerError, "Failed to get device")
    
    
    ```diff
    
  •   return fiber.NewError(fiber.StatusInternalServerError, "failed to export inbox")
    
  •   return fiber.NewError(fiber.StatusInternalServerError, "Failed to export inbox")
    
    
    
  1. Optional: use errorHandler for non-ErrNotFound device/export errors

    For Get, only the ErrNotFound branch explicitly returns a 400; all other device-level failures are turned into 500s via fiber.NewError, even though errorHandler is set up to map devices.ErrInvalidFilter, devices.ErrInvalidUser, and devices.ErrMoreThanOne to 400.

    If you want consistent treatment across endpoints, you could:

    • Keep the explicit 400 for invalid device ID, and
    • For other errors from Get/ExportInbox, log and return a wrapped error so errorHandler can classify them:
  • if err != nil {
  •   if errors.Is(err, devices.ErrNotFound) {
    
  •   	return fiber.NewError(fiber.StatusBadRequest, "Invalid device ID")
    
  •   }
    
  •   h.Logger.Error("failed to get device", zap.Error(err), zap.String("user_id", user.ID))
    
  •   return fiber.NewError(fiber.StatusInternalServerError, "failed to get device")
    
  • }
  • if err != nil {
  •   if errors.Is(err, devices.ErrNotFound) {
    
  •   	return fiber.NewError(fiber.StatusBadRequest, "Invalid device ID")
    
  •   }
    
  •   h.Logger.Error("failed to get device", zap.Error(err), zap.String("user_id", user.ID))
    
  •   return fmt.Errorf("failed to get device: %w", err)
    
  • }

```diff
-	if expErr := h.messagesSvc.ExportInbox(device, req.Since, req.Until); expErr != nil {
-		h.Logger.Error("failed to export inbox", zap.Error(expErr), zap.String("user_id", user.ID))
-		return fiber.NewError(fiber.StatusInternalServerError, "failed to export inbox")
-	}
+	if expErr := h.messagesSvc.ExportInbox(device, req.Since, req.Until); expErr != nil {
+		h.Logger.Error("failed to export inbox", zap.Error(expErr), zap.String("user_id", user.ID))
+		return fmt.Errorf("failed to export inbox: %w", expErr)
+	}

That keeps client-visible 400 vs 500 behavior centralized in errorHandler.


196-200: Capitalize 500 error messages for consistency

The user-facing messages on internal failures are still lowercased here, while other error messages in this file are capitalized:

  • Line 199: "failed to retrieve message history"
  • Line 231: "failed to get message state"

To align with the rest of the handlers and prior review feedback, consider:

-		return fiber.NewError(fiber.StatusInternalServerError, "failed to retrieve message history")
+		return fiber.NewError(fiber.StatusInternalServerError, "Failed to retrieve message history")
-		return fiber.NewError(fiber.StatusInternalServerError, "failed to get message state")
+		return fiber.NewError(fiber.StatusInternalServerError, "Failed to get message state")

Also applies to: 224-231


275-313: errorHandler mapping looks correct; tweak message casing and optionally surface underlying validation text

The new errorHandler middleware is a solid centralization of error-to-status mapping, and switching to a value-typed messages.ValidationError target:

var msgValidationError messages.ValidationError
case errors.As(err, &msgValidationError):

fixes the earlier bug where errors.As would never match the non-pointer type. That part looks correct.

Two small follow-ups:

  1. Capitalize the generic 500 message (already noted in prior review):

  • h.Logger.Error("failed to handle request", zap.Error(err))
  • return fiber.NewError(fiber.StatusInternalServerError, "failed to handle request")
  • h.Logger.Error("failed to handle request", zap.Error(err))
  • return fiber.NewError(fiber.StatusInternalServerError, "Failed to handle request")

2. **Optional: avoid leaking handler-level context in 400 messages**

For `messages.ValidationError` / `ErrNoContent` / repository errors you currently return `fiber.NewError(..., err.Error())`. If `err` was wrapped with extra context (e.g., `"failed to enqueue message: %w"`), the client sees that whole string.

If you prefer cleaner, domain-only messages on 4xx responses, you could instead use the matched value where available:

```diff
-	case errors.As(err, &msgValidationError):
-		fallthrough
+	case errors.As(err, &msgValidationError):
+		return fiber.NewError(fiber.StatusBadRequest, msgValidationError.Error())
+
+	case errors.Is(err, messages.ErrMultipleMessagesFound):
+		fallthrough
 case errors.Is(err, messages.ErrNoContent):
-		return fiber.NewError(fiber.StatusBadRequest, err.Error())
+		return fiber.NewError(fiber.StatusBadRequest, err.Error())

and keep err.Error() for the non-typed cases, where the additional context is useful.

🧹 Nitpick comments (1)
internal/sms-gateway/handlers/messages/3rdparty.go (1)

84-98: Optional: rely more on errorHandler to avoid double logging/wrapping

The GetAny/Enqueue branches log at the handler level and then wrap the domain error with fmt.Errorf("failed to ...: %w", err). The errorHandler will later classify the wrapped error and, for “unknown” cases, log again, which can lead to duplicate logs and somewhat noisy 500 diagnostics.

If you want to lean fully on the middleware for classification and most logging, you could:

  • Keep the contextual log at this level for domain-classified errors only (e.g., when errors.Is(err, devices.ErrNotFound)), and
  • For everything else, return the raw/just-wrapped error and let errorHandler do the logging and response mapping.

For example:

-	if err != nil {
-		h.Logger.Error(
-			"failed to select device",
-			zap.Error(err),
-			zap.String("user_id", user.ID),
-			zap.String("device_id", req.DeviceID),
-		)
-
-		return fmt.Errorf("failed to select device: %w", err)
-	}
+	if err != nil {
+		h.Logger.Error(
+			"failed to select device",
+			zap.Error(err),
+			zap.String("user_id", user.ID),
+			zap.String("device_id", req.DeviceID),
+		)
+		return fmt.Errorf("failed to select device: %w", err)
+	}

…and similarly for the enqueue error, or even just return err if the extra context in the error string is not needed on the client side (since it’s already in the log).

Also applies to: 130-143

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 53f7d3e and 8d96fbf.

📒 Files selected for processing (2)
  • internal/sms-gateway/handlers/messages/3rdparty.go (8 hunks)
  • internal/sms-gateway/openapi/docs.go (6 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
internal/sms-gateway/handlers/messages/3rdparty.go (6)
internal/sms-gateway/handlers/base/handler.go (1)
  • Handler (15-18)
internal/sms-gateway/modules/messages/service.go (1)
  • EnqueueOptions (22-24)
internal/sms-gateway/modules/messages/errors.go (2)
  • ValidationError (9-9)
  • ErrNoContent (6-6)
internal/sms-gateway/modules/messages/repository.go (3)
  • ErrMultipleMessagesFound (18-18)
  • ErrMessageNotFound (16-16)
  • ErrMessageAlreadyExists (17-17)
internal/sms-gateway/modules/devices/repository.go (3)
  • ErrNotFound (14-14)
  • ErrInvalidFilter (15-15)
  • ErrMoreThanOne (16-16)
internal/sms-gateway/modules/devices/errors.go (1)
  • ErrInvalidUser (6-6)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (4)
  • GitHub Check: Lint
  • GitHub Check: Benchmark
  • GitHub Check: E2E
  • GitHub Check: Analyze (go)
🔇 Additional comments (9)
internal/sms-gateway/openapi/docs.go (6)

1-2: Generated file – verify source annotations.

This file is auto-generated by swaggo/swag from Go source code annotations. The changes appear consistent with the PR's health endpoint and enum enhancements. Ensure the source annotations in handler files are correct and this file is regenerated as part of the build process.


124-148: Improved health endpoint semantics.

The /3rdparty/v1/health endpoint has been updated to use 503 (Service Unavailable) instead of 500 for readiness failures, which is the correct HTTP status code for readiness probes. The description and summary now clearly indicate this is a readiness probe, aligning with the new dedicated health endpoints.


788-866: Excellent addition of Kubernetes-style health probes.

The three new health endpoints (/health/live, /health/ready, /health/startup) follow Kubernetes health check best practices:

  • Liveness probe: Determines if the service is running and should be restarted if failing
  • Readiness probe: Determines if the service can serve traffic
  • Startup probe: Determines if initialization is complete

All endpoints have consistent structure with appropriate 200/503 responses. This is a valuable enhancement for cloud-native deployments.


1279-1303: LGTM – MessagePriority enum enhanced with format and descriptions.

The addition of "format": "int32" and x-enum-descriptions improves the OpenAPI specification for code generation and API documentation. The description for PriorityBypassThreshold (value 100) appropriately highlights its special behavior of bypassing limits and delays.


1398-1428: LGTM – ProcessingState enum enhanced with clear descriptions.

The x-enum-descriptions array provides helpful context for all five processing states, particularly clarifying that "Processed" means "received by device." This improves the API documentation and helps consumers understand the message lifecycle.


1686-1686: API title updated to reflect simplified branding.

The SwaggerInfo.Title has been changed from "SMS Gateway for Android™ API" to "SMSGate API", simplifying the API name. This appears to be part of the broader rebranding effort in this migration PR.

internal/sms-gateway/handlers/messages/3rdparty.go (3)

44-53: Constructor wiring matches fx.In pattern and base.Handler usage

NewThirdPartyController cleanly wires messagesSvc, devicesSvc, and the embedded base.Handler (logger/validator), consistent with other 3rdparty controllers and Fx’s fx.In pattern. No issues from a construction or dependency-injection standpoint.


72-82: Validation errors correctly normalized to HTTP 400

Routing both query and body parsing/validation failures through fiber.NewError(fiber.StatusBadRequest, err.Error()) standardizes bad input handling and lets the error middleware pass Fiber’s structured 400 back to the client. This is a good fit with the new errorHandler middleware.


315-323: Register wiring with errorHandler middleware looks coherent

Registering router.Use(h.errorHandler) before the route handlers ensures all errors propagate through the centralized mapping without changing the existing userauth wrappers or route signatures. The naming (route3rdPartyGetMessage) and usage in GetRouteURL remain consistent.

@github-actions
Copy link

github-actions bot commented Nov 17, 2025

🤖 Pull request artifacts

Platform File
🐳 Docker GitHub Container Registry
🍎 Darwin arm64 server_Darwin_arm64.tar.gz
🍎 Darwin x86_64 server_Darwin_x86_64.tar.gz
🐧 Linux arm64 server_Linux_arm64.tar.gz
🐧 Linux i386 server_Linux_i386.tar.gz
🐧 Linux x86_64 server_Linux_x86_64.tar.gz
🪟 Windows arm64 server_Windows_arm64.zip
🪟 Windows i386 server_Windows_i386.zip
🪟 Windows x86_64 server_Windows_x86_64.zip

@capcom6 capcom6 force-pushed the actions/migrate-to-goreleaser branch from 3e4ac02 to bd00981 Compare November 18, 2025 04:44
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (3)
internal/sms-gateway/modules/auth/service.go (1)

127-137: Fix fmt.Errorf with multiple %w verbs at line 142

The AuthorizeDevice function wraps errors using two %w verbs: fmt.Errorf("%w: %w", ErrAuthorizationFailed, err). In Go's error formatting, only the last %w chains with errors.Is/errors.As, so callers can detect only err, not the ErrAuthorizationFailed sentinel. This defeats the purpose of the sentinel error.

Change to use a single %w and format the other value as plain text:

- return device, fmt.Errorf("%w: %w", ErrAuthorizationFailed, err)
+ return device, fmt.Errorf("%w: %v", ErrAuthorizationFailed, err)

or wrap the actual error:

return device, fmt.Errorf("authorize device: %w", err)

The rest of AuthorizeDevice (including the timeout-bound SetOnline call) looks good.

internal/sms-gateway/modules/devices/repository.go (1)

45-54: Inconsistent error handling in Exists method.

The repository defines ErrNotFound = errors.New("record not found") at line 14, but the Exists method still checks for gorm.ErrRecordNotFound at line 47. This creates inconsistency:

  • The custom ErrNotFound defined in this package is never returned by Exists
  • Other methods like Get (line 63) return the custom ErrNotFound
  • Callers checking for devices.ErrNotFound won't catch the not-found case in Exists

Either:

  1. Update Exists to check errors.Is(err, ErrNotFound) and ensure GORM errors are wrapped, or
  2. Document that Exists intentionally uses different error semantics (returns false with nil error for not found)

The current behavior (returning false, nil when not found) appears intentional based on the doc comment, but the inconsistency with the package-level ErrNotFound definition is confusing.

</invoke_comment_end -->

pkg/cache/memory.go (1)

81-115: Potential data race between Get fast path and TTL‑updating Get calls

The fast path (len(opts) == 0) grabs item under RLock and then releases the lock before getItem calls item.isExpired(time.Now()). In the options path, when TTL options are used, the same *memoryItem’s validUntil field is mutated under a write lock. That means one goroutine can read item.validUntil while another writes it, which is a data race.

To avoid the race without broad refactors, update the TTL path to create a new memoryItem and replace the map entry instead of mutating the existing one in place, so previously returned pointers are effectively immutable:

 func (m *MemoryCache) Get(_ context.Context, key string, opts ...GetOption) ([]byte, error) {
 	return m.getValue(func() (*memoryItem, bool) {
@@
-		m.mux.Lock()
-		item, ok := m.items[key]
-
-		if ok && o.delete {
-			delete(m.items, key)
-		} else if ok && !item.isExpired(time.Now()) {
-			switch {
-			case o.validUntil != nil:
-				item.validUntil = *o.validUntil
-			case o.setTTL != nil:
-				item.validUntil = time.Now().Add(*o.setTTL)
-			case o.updateTTL != nil:
-				item.validUntil = item.validUntil.Add(*o.updateTTL)
-			case o.defaultTTL:
-				item.validUntil = time.Now().Add(m.ttl)
-			}
-		}
-		m.mux.Unlock()
-
-		return item, ok
+		now := time.Now()
+
+		m.mux.Lock()
+		item, ok := m.items[key]
+
+		if ok && o.delete {
+			delete(m.items, key)
+			m.mux.Unlock()
+			return nil, false
+		}
+
+		if ok && !item.isExpired(now) {
+			newValidUntil := item.validUntil
+
+			switch {
+			case o.validUntil != nil:
+				newValidUntil = *o.validUntil
+			case o.setTTL != nil:
+				newValidUntil = now.Add(*o.setTTL)
+			case o.updateTTL != nil:
+				newValidUntil = newValidUntil.Add(*o.updateTTL)
+			case o.defaultTTL:
+				newValidUntil = now.Add(m.ttl)
+			}
+
+			// Create a new item to keep previously returned pointers immutable.
+			item = &memoryItem{
+				value:      item.value,
+				validUntil: newValidUntil,
+			}
+			m.items[key] = item
+		}
+
+		m.mux.Unlock()
+		return item, ok
 	})
 }

This keeps external behavior the same while avoiding concurrent reads/writes of memoryItem.validUntil.

♻️ Duplicate comments (14)
.golangci.yml (1)

347-350: Remove dead configuration for disabled linter (revisited from previous review).

The inamedparam linter is disabled (line 127 is commented out), but its settings remain configured here with no effect. This orphaned block should be removed to keep the configuration clean and avoid future confusion.

Apply this diff to remove the dead configuration:

-    inamedparam:
-      # Skips check for interface methods with only a single parameter.
-      # Default: false
-      skip-single-param: true
-

Alternatively, if inamedparam is intended to be enabled, uncomment line 127 and keep (or adjust) this configuration as needed.

internal/sms-gateway/modules/auth/service.go (1)

37-50: Handle or document ignored nanoid error and new caches

The addition of usersCache and initializing both caches in New looks consistent and keeps cleanup centralized via Run/clean.

However, nanoid.Standard(idLen) still has its error silently discarded:

const idLen = 21
idgen, _ := nanoid.Standard(idLen)

Even if 21 is within the safe range today, either:

  • Check and handle/log the error; or
  • Add a brief comment explaining why this can’t fail with a compile-time constant idLen in the supported range, so ignoring it is intentional.

This keeps future maintainers from wondering if an error was forgotten.

Also applies to: 52-67

internal/version/version.go (1)

7-7: Grammar error persists from previous review.

The comment still contains the grammatical error "when build" which should be "during build" or "when building". This was already flagged in a previous review but remains unfixed.

Apply this diff to fix the grammar:

-// This information will be collected when build, by `-ldflags "-X main.appVersion=0.1"`.
+// This information will be collected during build, by `-ldflags "-X main.appVersion=0.1"`.
.goreleaser.yaml (1)

38-60: Docker image tagging mismatch remains unresolved.

The dockers_v2 configuration produces multi-arch manifest images with tags like latest, v{{ .Major }}, etc., but does not generate per-architecture tags such as latest-amd64 or latest-arm64. If the pr.yml workflow (referenced in past reviews) still attempts to use architecture-specific tags, those operations will fail.

Ensure that:

  1. The GoReleaser config matches what downstream workflows expect, OR
  2. Workflows are updated to use only the tags GoReleaser actually produces

Based on the past review comment, this issue was already identified but appears unresolved in the current draft PR.

</invoke_comment_end -->

internal/sms-gateway/handlers/mobile.go (1)

274-276: Handle or explicitly justify ignoring the nanoid.Standard error.

The ID generator is still initialized as:

const idGenSize = 21
idGen, _ := nanoid.Standard(idGenSize)

Silently discarding the error can hide unexpected initialization failures. Either handle the error (and fail fast) or add a short comment explaining why it is safe to ignore for this constant size.

For example, to handle it:

- const idGenSize = 21
- idGen, _ := nanoid.Standard(idGenSize)
+ const idGenSize = 21
+ idGen, err := nanoid.Standard(idGenSize)
+ if err != nil {
+   params.Logger.Fatal("failed to initialize ID generator", zap.Error(err))
+ }

Or, if you’re certain it cannot fail for 21:

  const idGenSize = 21
- idGen, _ := nanoid.Standard(idGenSize)
+ // nanoid.Standard with size 21 is guaranteed not to return an error.
+ idGen, _ := nanoid.Standard(idGenSize)
Confirm from the `github.com/jaevor/go-nanoid` documentation whether `Standard(21)` can ever return a non-nil error, and if not, note the guarantee so it can be documented next to this call.
.github/workflows/pr.yml (2)

91-103: Pin the custom S3 upload action to a stable tag or SHA.

capcom6/upload-s3-action@master is still pointing at a moving branch, which is a supply‑chain risk and can introduce non‑reproducible builds. Prefer pinning to a specific release tag or commit SHA.

Example (replace with the actual tag or SHA you intend to support):

-      - name: Upload to S3
-        uses: capcom6/upload-s3-action@master
+      - name: Upload to S3
+        uses: capcom6/upload-s3-action@vX.Y.Z # or a specific commit SHA
Find the latest stable release tag (or recommended pinning approach) for the GitHub Action `capcom6/upload-s3-action` so it can be referenced immutably instead of `@master`.

105-118: Verify that GoReleaser produces the latest-arm64/latest-amd64 tags used here.

The Push images step assumes images exist with :latest-arm64 and :latest-amd64 tags:

docker tag $REG/$PROJECT:latest-arm64 ...
docker tag $REG/$PROJECT:latest-amd64 ...

If your GoReleaser docker config only emits :latest (multi‑arch) and versioned tags, these per‑arch tags won’t exist and the step will fail. Either:

  • Update .goreleaser.yaml to build/publish per‑arch images with those tags, or
  • Adjust this step to use the actual tags GoReleaser produces (or rely on GoReleaser’s own docker publishing).
#!/bin/bash
# Verify whether GoReleaser config defines per-arch docker tags matching latest-{amd64,arm64}
fd -e yml -e yaml | grep -i goreleaser || true
if [ -f .goreleaser.yaml ]; then
  echo "=== Checking .goreleaser.yaml for docker image tags ==="
  rg -n "dockers" .goreleaser.yaml -n -C3 || true
  rg -n "latest-arm64|latest-amd64" .goreleaser.yaml || true
fi
internal/sms-gateway/modules/events/module.go (1)

16-17: fx.Private placement likely leaves newMetrics exported (same concern as earlier review).

As noted in a previous review, in

fx.Provide(newMetrics, fx.Private),

fx.Private only affects constructors after it in the same fx.Provide call, so it won’t actually make newMetrics private. If you want newMetrics to be module-private, the usual pattern is:

fx.Provide(
    fx.Private,
    newMetrics,
)

If exporting metrics broadly is intentional, then no change is needed.

Confirm from the uber-go/fx documentation: does placing `fx.Private` *before* constructors in `fx.Provide` (e.g., `fx.Provide(fx.Private, newMetrics)`) mark those constructors private, while placing it after (`fx.Provide(newMetrics, fx.Private)`) has no effect on the preceding constructors?
internal/config/module.go (1)

90-91: Fix API path defaulting to respect user configuration.

The current logic overwrites cfg.HTTP.API.Path whenever cfg.HTTP.API.Host is empty, even if the user explicitly configured a custom path. This will surprise users who set a path but rely on the default (empty) host.

Apply this diff to default the path only when it's actually empty:

-		if cfg.HTTP.API.Host == "" {
+		if cfg.HTTP.API.Path == "" {
 			cfg.HTTP.API.Path = "/api"
 		}

This ensures user-supplied paths are preserved regardless of the host configuration.

internal/sms-gateway/modules/push/upstream/client.go (2)

29-35: Redundant zero-value initialization.

Lines 32-33 explicitly initialize client: nil and mux: sync.Mutex{}, but these are the zero values for their respective types and don't require explicit initialization.

Apply this diff:

 func New(options map[string]string) (*Client, error) {
 	return &Client{
 		options: options,
-		client:  nil,
-		mux:     sync.Mutex{},
 	}, nil
 }

62-62: Remove unnecessary type conversion.

UpstreamPushRequest is a type alias for []smsgateway.PushNotification. Since payload is already the correct slice type, the explicit conversion is redundant and the nolint:unconvert directive suppresses a valid linter warning.

Apply this diff:

-	payloadBytes, err := json.Marshal(smsgateway.UpstreamPushRequest(payload)) //nolint:unconvert //type checking
+	payloadBytes, err := json.Marshal(payload)
internal/sms-gateway/modules/messages/service.go (2)

197-199: Clarify error handling contract when insert fails.

Returning both a non-nil state and an error on insert failure (lines 197-199) violates the standard Go pattern where functions return (nil, error) on failure. While the current caller at handlers/messages/3rdparty.go:130 safely checks the error first, this pattern is error-prone and relies on caller discipline.

A past review flagged this issue and was marked as "Addressed in commit 7db6313", but the code still returns (state, insErr).

Apply this diff to follow Go conventions:

 	if insErr := s.messages.Insert(msg); insErr != nil {
-		return state, insErr
+		return nil, insErr
 	}

236-241: Address integer overflow risk in TTL calculation.

The nolint:gosec suppression on line 237 is unjustified. The code multiplies user-controlled *message.TTL (uint64) by time.Second, then casts to time.Duration (int64). Without bounds checking, a TTL value exceeding ~9,223,372,036 seconds (~292 years) will cause silent integer overflow.

A past review flagged this issue and was marked as "Addressed in commit 7db6313", but the code still lacks bounds validation.

Apply this diff to add explicit bounds validation:

 	validUntil := message.ValidUntil
 	if message.TTL != nil && *message.TTL > 0 {
-		//nolint:gosec // not a problem
+		const maxTTL = 315360000 // ~10 years in seconds
+		if *message.TTL > maxTTL {
+			return nil, fmt.Errorf("TTL exceeds maximum allowed value of %d seconds", maxTTL)
+		}
 		validUntil = anys.AsPointer(
 			time.Now().Add(time.Duration(*message.TTL) * time.Second),
 		)
 	}
internal/sms-gateway/handlers/messages/params.go (1)

24-30: Fix date-range validation to compare parsed timestamps.

Line 25 compares p.StartDate > p.EndDate lexicographically, which can incorrectly reject valid date ranges when timezone offsets differ. For example, "2025-11-10T12:00:00+02:00" is lexicographically greater than "2025-11-10T11:00:00+00:00" even though they represent the same moment in time.

A past review flagged this issue and was marked as addressed, but the code still uses string comparison.

Apply this diff to parse and compare chronologically:

 func (p *thirdPartyGetQueryParams) Validate() error {
-	if p.StartDate != "" && p.EndDate != "" && p.StartDate > p.EndDate {
-		return errors.New("`from` date must be before `to` date") //nolint:err113 // won't be used directly
+	if p.StartDate != "" && p.EndDate != "" {
+		from, err := time.Parse(time.RFC3339, p.StartDate)
+		if err != nil {
+			return errors.New("`from` date must be before `to` date") //nolint:err113 // won't be used directly
+		}
+
+		to, err := time.Parse(time.RFC3339, p.EndDate)
+		if err != nil {
+			return errors.New("`from` date must be before `to` date") //nolint:err113 // won't be used directly
+		}
+
+		if from.After(to) {
+			return errors.New("`from` date must be before `to` date") //nolint:err113 // won't be used directly
+		}
 	}
 
 	return nil
 }
🧹 Nitpick comments (17)
internal/sms-gateway/handlers/base/handler_test.go (1)

19-19: Formatting change: struct tag alignment.

The extra spaces in struct tags appear to be for visual alignment. While this is cosmetic and has no functional impact, ensure consistent formatting is applied across the codebase.

Also applies to: 24-24, 29-29, 33-33

Makefile (1)

26-28: Coverage generation now re-runs tests.

The coverage target depends on test, so invoking make coverage will re-run all tests. While this ensures fresh coverage data, consider whether this is the intended behavior. If tests have already been run, you might prefer a target that generates coverage from an existing coverage.out without re-running tests.

If independent coverage generation is desired, consider adding an optional target:

coverage-report: ## Generate coverage report from existing data
	go tool cover -func=coverage.out
	go tool cover -html=coverage.out -o coverage.html

This allows users to regenerate reports without re-running tests, while keeping the coverage target for full coverage pipeline execution.

internal/sms-gateway/handlers/upstream.go (1)

97-106: Consider making rate limit values configurable.

The rate limit constants are an improvement over inline magic numbers. However, rate limits are typically operational parameters that benefit from being configurable per environment without code changes.

Consider adding fields to the Config struct (e.g., UpstreamRateLimit and UpstreamRateDuration) to allow runtime configuration while keeping these constants as defaults.

internal/sms-gateway/modules/db/health.go (1)

20-21: Optional: Redundant field initialization.

The explicit initialization failedPings: atomic.Int64{} is redundant since Go zero-initializes all struct fields automatically. The atomic.Int64 will have the same zero value whether explicitly initialized or not.

You can simplify by removing this line, or keep it for explicitness. Both approaches are valid.

Apply this diff to remove the redundant initialization:

 func newHealth(db *sql.DB) *health {
 	return &health{
 		db: db,
-
-		failedPings: atomic.Int64{},
 	}
 }
internal/sms-gateway/handlers/middlewares/userauth/userauth.go (1)

115-124: Align unauthorized handling with other middlewares (optional)

WithUser currently returns fiber.NewError(fiber.StatusUnauthorized, "Unauthorized") while NewBasic, NewCode, and UserRequired use fiber.ErrUnauthorized. For consistency (and to reuse Fiber’s shared error value), consider switching this branch to return fiber.ErrUnauthorized instead.

internal/sms-gateway/pubsub/pubsub.go (2)

4-18: Sentinel ErrInvalidScheme definition looks good; consider clarifying/aligning error contracts

Importing "errors" and introducing ErrInvalidScheme as an exported sentinel is a solid move for callers to distinguish config errors via errors.Is. You might want to (a) document in this package that invalid schemes are treated as configuration errors, and/or (b) consider whether this should align with the existing pubsub.ErrInvalidConfig contract so higher layers don’t have to special‑case multiple config‑type sentinels.

Please double‑check other callers constructing pubsub configs to ensure they handle ErrInvalidScheme (and/or pubsub.ErrInvalidConfig) consistently in their error mapping logic.


24-50: Centralized error handling in New is correct; small readability tweaks are possible

The refactor to parse once, create pubSub in the switch, and then wrap any backend error with "failed to create pubsub" is clean and correct. A couple of small readability improvements you might consider:

  • Build the options slice directly instead of creating then appending.
  • In the "memory" branch, avoid the slightly unusual pubSub, err = pubsub.NewMemory(opts...), nil multi‑assignment and just assign pubSub (leaving err untouched, which is already nil after the parse check).

Example diff for both tweaks:

-	opts := []pubsub.Option{}
-	opts = append(opts, pubsub.WithBufferSize(config.BufferSize))
+	opts := []pubsub.Option{
+		pubsub.WithBufferSize(config.BufferSize),
+	}
@@
 	var pubSub PubSub
 	switch u.Scheme {
 	case "memory":
-		pubSub, err = pubsub.NewMemory(opts...), nil
+		pubSub = pubsub.NewMemory(opts...)
 	case "redis":

Behavior remains the same but is a bit easier to read at a glance.

If you adopt these tweaks, please rerun the existing tests around pubsub initialization (both memory and redis) to confirm there are no regressions.

internal/sms-gateway/modules/auth/repository.go (1)

18-29: Pointer returns are fine; consider clarifying naming

The switch to *models.User (with new(models.User) and Take(user)) and Model((*models.User)(nil)) is idiomatic for GORM and matches the service’s pointer-based API.

One small nit: GetByLogin still queries WHERE id = ?, so “login” effectively is the primary key. If you ever introduce a separate login/username field, this method name vs. query mismatch will become confusing; consider either renaming the method/comment or explicitly documenting that login is the user ID.

Also applies to: 35-37

internal/sms-gateway/modules/auth/cache.go (1)

28-35: Align cache error semantics with caller expectations

usersCache.Get wraps all underlying errors as "failed to get user from cache: %w", but Service.AuthorizeUser treats any non-nil error as a simple cache miss and never logs it. That means the extra wrapping work isn’t observable and also hides the ability to distinguish “not found” vs real cache failures if you later decide to log or metric them.

Consider one of:

  • Returning the underlying error as-is and letting callers decide how to handle/log it; or
  • Mapping a “not found” sentinel from cache.Cache to (models.User{}, nil) while only wrapping/logging unexpected failures.

This would make cache behavior clearer and slightly simplify error handling.

Also applies to: 37-51

internal/sms-gateway/modules/push/service.go (1)

16-21: Debounce clamping makes the Enqueue doc comment stale.

New now enforces config.Debounce = max(config.Debounce, defaultDebounce) with defaultDebounce = 5 * time.Second, so a zero debounce is no longer possible. The Enqueue comment still says it “immediately sends all messages if the debounce is 0”, which is now misleading.

Either update the comment to reflect the clamped behavior, or reintroduce a special “0 means immediate send” path if that semantic is still desired.

Also applies to: 60-62, 75-89, 91-91

internal/sms-gateway/modules/push/fcm/utils.go (1)

7-19: Aligning with client.Event is correct; consider renaming local json variable

Switching to client.Event matches the new push client abstraction and keeps behavior intact. The only small nit is the json variable name shadowing the imported package; renaming it (e.g., to dataJSON) would slightly improve readability:

-func eventToMap(event client.Event) (map[string]string, error) {
-	json, err := json.Marshal(event.Data)
+func eventToMap(event client.Event) (map[string]string, error) {
+	dataJSON, err := json.Marshal(event.Data)
 	if err != nil {
 		return nil, fmt.Errorf("failed to marshal event data: %w", err)
 	}
 
 	return map[string]string{
 		"event": string(event.Type),
-		"data":  string(json),
+		"data":  string(dataJSON),
 	}, nil
 }
internal/sms-gateway/modules/webhooks/errors.go (1)

18-20: Validation error formatting and detection are solid

Formatting ValidationError as invalid "field" = "value": innerErr is log‑friendly, and using errors.As(err, new(ValidationError)) allows callers to detect validation errors through wrapped chains without relying on string matching.

If you ever need access to the concrete ValidationError, consider switching IsValidationError to accept a *ValidationError out‑param, e.g., func AsValidationError(err error, out *ValidationError) bool.

Also applies to: 34-36

internal/sms-gateway/modules/push/client/types.go (1)

1-13: Push client abstraction looks solid; consider clarifying Send error slice semantics.

The Client interface and Message/Event types are well-shaped for FCM/upstream backends and the context-aware lifecycle is clear. The only thing I’d consider adding (in comments or docs) is an explicit contract for Send — e.g., whether the []error is always the same length as messages, whether it can be nil on total failure, and how callers should interpret per-message vs global errors. That would make downstream usage less ambiguous.

internal/sms-gateway/modules/settings/models.go (1)

11-24: DeviceSettings refactor and constructor look good.

Embedding models.TimedModel directly into DeviceSettings is consistent with the shared model patterns and gives you created/updated timestamps on this table. The NewDeviceSettings constructor nicely captures the required fields while intentionally leaving TimedModel/User unset for GORM/DB to manage. The switch to db.AutoMigrate(new(DeviceSettings)) is also fine and matches the style used elsewhere.

Also applies to: 27-31

internal/sms-gateway/modules/sse/errors.go (1)

1-7: Good use of a static sentinel error for SSE “no connection” cases.

Defining ErrNoConnection as a package-level sentinel is the right move for letting callers classify errors.Is(err, ErrNoConnection). As a small polish, you might add a brief doc comment and/or make the message slightly more contextual (e.g. "sse: no connection") so logs are self-explanatory, but the current form is functionally fine.

internal/sms-gateway/modules/sse/service.go (1)

46-46: Optional: Redundant mutex initialization.

Explicitly initializing mu: sync.RWMutex{} is redundant since the zero value of sync.RWMutex is already ready to use. You can safely omit this field from the struct literal.

Apply this diff if you prefer to remove the redundancy:

 return &Service{
 	config: config,
-
-	mu:          sync.RWMutex{},
 	connections: make(map[string][]*sseConnection),
 
 	logger:  logger,
internal/sms-gateway/modules/push/fcm/client.go (1)

21-27: Redundant zero-value initialization.

Lines 24-25 explicitly initialize client: nil and mux: sync.Mutex{}, but these are the zero values for their respective types and don't require explicit initialization.

Apply this diff:

 func New(options map[string]string) (*Client, error) {
 	return &Client{
 		options: options,
-		client:  nil,
-		mux:     sync.Mutex{},
 	}, nil
 }
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 3e4ac02 and bd00981.

⛔ Files ignored due to path filters (1)
  • go.sum is excluded by !**/*.sum
📒 Files selected for processing (107)
  • .github/workflows/docker-build.yml (0 hunks)
  • .github/workflows/docker-publish.yml (0 hunks)
  • .github/workflows/go.yml (3 hunks)
  • .github/workflows/pr.yml (1 hunks)
  • .github/workflows/release.yml (2 hunks)
  • .gitignore (1 hunks)
  • .golangci.yml (1 hunks)
  • .goreleaser.yaml (1 hunks)
  • Dockerfile.goreleaser (1 hunks)
  • Makefile (1 hunks)
  • cmd/sms-gateway/main.go (2 hunks)
  • go.mod (0 hunks)
  • internal/config/config.go (3 hunks)
  • internal/config/module.go (1 hunks)
  • internal/sms-gateway/app.go (2 hunks)
  • internal/sms-gateway/cache/errors.go (1 hunks)
  • internal/sms-gateway/cache/factory.go (2 hunks)
  • internal/sms-gateway/handlers/base/handler.go (1 hunks)
  • internal/sms-gateway/handlers/base/handler_test.go (5 hunks)
  • internal/sms-gateway/handlers/converters/messages.go (1 hunks)
  • internal/sms-gateway/handlers/devices/3rdparty.go (4 hunks)
  • internal/sms-gateway/handlers/events/mobile.go (1 hunks)
  • internal/sms-gateway/handlers/health.go (2 hunks)
  • internal/sms-gateway/handlers/logs/3rdparty.go (2 hunks)
  • internal/sms-gateway/handlers/messages/3rdparty.go (8 hunks)
  • internal/sms-gateway/handlers/messages/mobile.go (3 hunks)
  • internal/sms-gateway/handlers/messages/params.go (3 hunks)
  • internal/sms-gateway/handlers/middlewares/userauth/userauth.go (2 hunks)
  • internal/sms-gateway/handlers/mobile.go (8 hunks)
  • internal/sms-gateway/handlers/module.go (1 hunks)
  • internal/sms-gateway/handlers/root.go (1 hunks)
  • internal/sms-gateway/handlers/settings/3rdparty.go (4 hunks)
  • internal/sms-gateway/handlers/settings/mobile.go (2 hunks)
  • internal/sms-gateway/handlers/upstream.go (4 hunks)
  • internal/sms-gateway/handlers/webhooks/3rdparty.go (4 hunks)
  • internal/sms-gateway/handlers/webhooks/mobile.go (2 hunks)
  • internal/sms-gateway/models/migration.go (2 hunks)
  • internal/sms-gateway/models/models.go (2 hunks)
  • internal/sms-gateway/models/module.go (1 hunks)
  • internal/sms-gateway/modules/auth/cache.go (1 hunks)
  • internal/sms-gateway/modules/auth/errors.go (1 hunks)
  • internal/sms-gateway/modules/auth/module.go (1 hunks)
  • internal/sms-gateway/modules/auth/repository.go (1 hunks)
  • internal/sms-gateway/modules/auth/service.go (7 hunks)
  • internal/sms-gateway/modules/auth/types.go (1 hunks)
  • internal/sms-gateway/modules/db/health.go (3 hunks)
  • internal/sms-gateway/modules/db/module.go (2 hunks)
  • internal/sms-gateway/modules/devices/repository.go (5 hunks)
  • internal/sms-gateway/modules/devices/repository_filter.go (1 hunks)
  • internal/sms-gateway/modules/devices/service.go (6 hunks)
  • internal/sms-gateway/modules/events/errors.go (1 hunks)
  • internal/sms-gateway/modules/events/metrics.go (3 hunks)
  • internal/sms-gateway/modules/events/module.go (1 hunks)
  • internal/sms-gateway/modules/events/service.go (7 hunks)
  • internal/sms-gateway/modules/events/types.go (2 hunks)
  • internal/sms-gateway/modules/messages/cache.go (2 hunks)
  • internal/sms-gateway/modules/messages/converters.go (1 hunks)
  • internal/sms-gateway/modules/messages/errors.go (1 hunks)
  • internal/sms-gateway/modules/messages/models.go (7 hunks)
  • internal/sms-gateway/modules/messages/module.go (1 hunks)
  • internal/sms-gateway/modules/messages/repository.go (7 hunks)
  • internal/sms-gateway/modules/messages/repository_filter.go (2 hunks)
  • internal/sms-gateway/modules/messages/service.go (7 hunks)
  • internal/sms-gateway/modules/messages/service_test.go (0 hunks)
  • internal/sms-gateway/modules/messages/workers.go (5 hunks)
  • internal/sms-gateway/modules/metrics/handler.go (1 hunks)
  • internal/sms-gateway/modules/metrics/module.go (1 hunks)
  • internal/sms-gateway/modules/push/client.go (1 hunks)
  • internal/sms-gateway/modules/push/client/types.go (1 hunks)
  • internal/sms-gateway/modules/push/fcm/client.go (4 hunks)
  • internal/sms-gateway/modules/push/fcm/errors.go (1 hunks)
  • internal/sms-gateway/modules/push/fcm/utils.go (1 hunks)
  • internal/sms-gateway/modules/push/module.go (2 hunks)
  • internal/sms-gateway/modules/push/service.go (10 hunks)
  • internal/sms-gateway/modules/push/types.go (3 hunks)
  • internal/sms-gateway/modules/push/upstream/client.go (4 hunks)
  • internal/sms-gateway/modules/settings/models.go (1 hunks)
  • internal/sms-gateway/modules/settings/module.go (1 hunks)
  • internal/sms-gateway/modules/settings/repository.go (5 hunks)
  • internal/sms-gateway/modules/settings/service.go (3 hunks)
  • internal/sms-gateway/modules/settings/utils.go (4 hunks)
  • internal/sms-gateway/modules/sse/config.go (2 hunks)
  • internal/sms-gateway/modules/sse/errors.go (1 hunks)
  • internal/sms-gateway/modules/sse/metrics.go (3 hunks)
  • internal/sms-gateway/modules/sse/module.go (1 hunks)
  • internal/sms-gateway/modules/sse/service.go (8 hunks)
  • internal/sms-gateway/modules/webhooks/errors.go (3 hunks)
  • internal/sms-gateway/modules/webhooks/models.go (1 hunks)
  • internal/sms-gateway/modules/webhooks/module.go (1 hunks)
  • internal/sms-gateway/modules/webhooks/repository.go (2 hunks)
  • internal/sms-gateway/modules/webhooks/repository_filter.go (1 hunks)
  • internal/sms-gateway/modules/webhooks/service.go (5 hunks)
  • internal/sms-gateway/online/metrics.go (5 hunks)
  • internal/sms-gateway/online/service.go (5 hunks)
  • internal/sms-gateway/openapi/docs.go (6 hunks)
  • internal/sms-gateway/pubsub/module.go (2 hunks)
  • internal/sms-gateway/pubsub/pubsub.go (2 hunks)
  • internal/version/version.go (1 hunks)
  • internal/worker/app.go (1 hunks)
  • internal/worker/config/config.go (1 hunks)
  • internal/worker/config/types.go (2 hunks)
  • internal/worker/executor/metrics.go (1 hunks)
  • internal/worker/executor/service.go (3 hunks)
  • internal/worker/locker/mysql.go (4 hunks)
  • pkg/cache/errors.go (1 hunks)
  • pkg/cache/memory.go (12 hunks)
  • pkg/cache/memory_bench_test.go (19 hunks)
⛔ Files not processed due to max files limit (18)
  • pkg/cache/memory_concurrency_test.go
  • pkg/cache/memory_edge_test.go
  • pkg/cache/memory_profile_test.go
  • pkg/cache/memory_test.go
  • pkg/cache/options.go
  • pkg/cache/redis.go
  • pkg/crypto/passwords.go
  • pkg/health/health.go
  • pkg/health/module.go
  • pkg/health/service.go
  • pkg/health/types.go
  • pkg/mysql/errors.go
  • pkg/pubsub/memory.go
  • pkg/pubsub/options.go
  • pkg/pubsub/pubsub.go
  • pkg/pubsub/redis.go
  • test/e2e/mobile_test.go
  • test/e2e/utils_test.go
💤 Files with no reviewable changes (4)
  • internal/sms-gateway/modules/messages/service_test.go
  • go.mod
  • .github/workflows/docker-build.yml
  • .github/workflows/docker-publish.yml
✅ Files skipped from review due to trivial changes (1)
  • internal/sms-gateway/modules/sse/metrics.go
🚧 Files skipped from review as they are similar to previous changes (34)
  • internal/sms-gateway/online/metrics.go
  • .gitignore
  • internal/sms-gateway/models/module.go
  • internal/sms-gateway/modules/events/types.go
  • Dockerfile.goreleaser
  • internal/worker/config/types.go
  • internal/sms-gateway/modules/webhooks/module.go
  • internal/worker/executor/metrics.go
  • internal/sms-gateway/modules/push/module.go
  • internal/sms-gateway/handlers/events/mobile.go
  • internal/sms-gateway/handlers/devices/3rdparty.go
  • internal/sms-gateway/modules/messages/module.go
  • internal/worker/locker/mysql.go
  • internal/sms-gateway/models/models.go
  • internal/sms-gateway/models/migration.go
  • internal/sms-gateway/modules/events/errors.go
  • internal/sms-gateway/modules/events/service.go
  • internal/sms-gateway/handlers/base/handler.go
  • internal/sms-gateway/handlers/settings/3rdparty.go
  • internal/sms-gateway/modules/auth/types.go
  • internal/sms-gateway/handlers/logs/3rdparty.go
  • internal/sms-gateway/pubsub/module.go
  • cmd/sms-gateway/main.go
  • internal/worker/config/config.go
  • internal/sms-gateway/modules/messages/workers.go
  • internal/sms-gateway/modules/messages/converters.go
  • internal/sms-gateway/handlers/converters/messages.go
  • internal/sms-gateway/modules/auth/module.go
  • internal/worker/app.go
  • pkg/cache/memory_bench_test.go
  • internal/sms-gateway/modules/settings/repository.go
  • .github/workflows/release.yml
  • internal/sms-gateway/cache/factory.go
  • internal/sms-gateway/modules/messages/repository_filter.go
🧰 Additional context used
🧠 Learnings (6)
📚 Learning: 2025-09-10T23:45:45.502Z
Learnt from: capcom6
Repo: android-sms-gateway/server PR: 178
File: internal/sms-gateway/online/module.go:14-16
Timestamp: 2025-09-10T23:45:45.502Z
Learning: fx.Decorate in uber-go/fx creates module-scoped decorated dependencies, not global modifications. Using fx.Decorate(func(log *zap.Logger) *zap.Logger { return log.Named("module_name") }) is a correct pattern for creating module-specific loggers that are scoped only to that module's dependency graph.

Applied to files:

  • internal/sms-gateway/modules/settings/module.go
  • internal/sms-gateway/handlers/module.go
  • internal/sms-gateway/modules/metrics/module.go
  • internal/sms-gateway/modules/events/module.go
📚 Learning: 2025-09-05T08:37:28.077Z
Learnt from: eznix86
Repo: android-sms-gateway/server PR: 172
File: deployments/kubernetes/sms-gateway-server/Chart.yaml:9-10
Timestamp: 2025-09-05T08:37:28.077Z
Learning: In the android-sms-gateway/server project, the Helm chart's appVersion in Chart.yaml is set to "latest" as a placeholder because CI automatically updates it with the actual version from git tags during the release process via the bump-chart-app-version.yaml workflow.

Applied to files:

  • .goreleaser.yaml
📚 Learning: 2025-07-22T00:31:28.796Z
Learnt from: capcom6
Repo: android-sms-gateway/server PR: 154
File: internal/sms-gateway/modules/sse/service.go:67-79
Timestamp: 2025-07-22T00:31:28.796Z
Learning: In the SSE service Send method (internal/sms-gateway/modules/sse/service.go), blocking on full channels is intentional and acceptable. Timeout control is handled at the calling code level through context, not within the Send method itself. The user prefers this approach over adding a default case that would drop messages when channels are full.

Applied to files:

  • internal/sms-gateway/modules/sse/service.go
  • internal/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.go
  • internal/sms-gateway/modules/sse/errors.go
📚 Learning: 2025-09-10T11:11:43.462Z
Learnt from: capcom6
Repo: android-sms-gateway/server PR: 178
File: pkg/cache/memory.go:59-74
Timestamp: 2025-09-10T11:11:43.462Z
Learning: In pkg/cache/memory.go, the cleanup method removes expired items first, then executes the provided callback function. This means methods like Drain that use cleanup don't need additional expiration filtering since expired items are already removed before the callback runs.

Applied to files:

  • internal/sms-gateway/online/service.go
  • pkg/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.go
  • pkg/cache/memory.go
🧬 Code graph analysis (45)
internal/sms-gateway/handlers/middlewares/userauth/userauth.go (1)
internal/sms-gateway/models/models.go (1)
  • User (18-24)
internal/sms-gateway/modules/push/client.go (5)
internal/sms-gateway/modules/push/fcm/client.go (2)
  • New (21-27)
  • Client (14-19)
internal/sms-gateway/modules/push/service.go (2)
  • New (43-73)
  • Config (23-30)
internal/sms-gateway/modules/push/upstream/client.go (2)
  • New (29-35)
  • Client (22-27)
internal/sms-gateway/modules/push/client/types.go (1)
  • Client (9-13)
internal/sms-gateway/modules/push/types.go (3)
  • Mode (10-10)
  • ModeFCM (13-13)
  • ModeUpstream (14-14)
internal/sms-gateway/modules/sse/module.go (2)
internal/sms-gateway/app.go (1)
  • Module (33-58)
internal/sms-gateway/modules/sse/service.go (2)
  • NewService (42-52)
  • Service (21-29)
internal/sms-gateway/modules/auth/cache.go (2)
internal/sms-gateway/models/models.go (1)
  • User (18-24)
internal/sms-gateway/modules/auth/service.go (2)
  • New (52-67)
  • Config (20-23)
internal/sms-gateway/modules/settings/utils.go (1)
internal/sms-gateway/pubsub/pubsub.go (1)
  • New (19-51)
internal/sms-gateway/modules/webhooks/repository.go (3)
internal/sms-gateway/modules/devices/repository.go (2)
  • NewRepository (23-27)
  • Repository (19-21)
internal/sms-gateway/modules/messages/repository.go (2)
  • NewRepository (24-28)
  • Repository (20-22)
internal/sms-gateway/modules/webhooks/models.go (1)
  • Webhook (11-25)
internal/sms-gateway/modules/settings/module.go (1)
internal/sms-gateway/app.go (1)
  • Module (33-58)
internal/sms-gateway/modules/settings/service.go (1)
internal/sms-gateway/modules/settings/models.go (1)
  • NewDeviceSettings (19-25)
internal/sms-gateway/modules/sse/config.go (1)
internal/config/config.go (1)
  • Config (10-20)
internal/sms-gateway/handlers/module.go (1)
internal/sms-gateway/app.go (1)
  • Module (33-58)
internal/sms-gateway/app.go (16)
internal/config/module.go (1)
  • Module (23-140)
internal/sms-gateway/handlers/module.go (1)
  • Module (15-41)
internal/sms-gateway/modules/db/module.go (1)
  • Module (16-26)
internal/sms-gateway/modules/events/module.go (1)
  • Module (10-39)
internal/sms-gateway/modules/messages/module.go (1)
  • Module (10-25)
internal/sms-gateway/modules/metrics/module.go (1)
  • Module (9-19)
internal/sms-gateway/modules/push/module.go (1)
  • Module (11-36)
internal/sms-gateway/modules/settings/module.go (1)
  • Module (9-23)
internal/sms-gateway/modules/sse/module.go (1)
  • Module (10-32)
internal/sms-gateway/modules/webhooks/module.go (1)
  • Module (9-20)
internal/sms-gateway/pubsub/module.go (1)
  • Module (11-33)
internal/sms-gateway/modules/auth/module.go (1)
  • Module (10-32)
internal/sms-gateway/modules/devices/module.go (1)
  • Module (8-18)
internal/sms-gateway/online/module.go (1)
  • Module (11-36)
internal/sms-gateway/cache/module.go (1)
  • Module (8-16)
internal/worker/app.go (1)
  • Run (19-28)
internal/sms-gateway/handlers/health.go (1)
pkg/health/types.go (1)
  • CheckDetail (54-64)
internal/sms-gateway/handlers/webhooks/3rdparty.go (4)
internal/sms-gateway/handlers/base/handler.go (1)
  • Handler (15-18)
internal/sms-gateway/modules/webhooks/models.go (1)
  • Webhook (11-25)
internal/sms-gateway/modules/webhooks/errors.go (1)
  • IsValidationError (34-36)
internal/sms-gateway/modules/webhooks/repository_filter.go (1)
  • WithExtID (7-11)
internal/sms-gateway/modules/auth/repository.go (1)
internal/sms-gateway/models/models.go (1)
  • User (18-24)
internal/sms-gateway/handlers/webhooks/mobile.go (5)
internal/sms-gateway/handlers/base/handler.go (1)
  • Handler (15-18)
internal/sms-gateway/modules/webhooks/service.go (1)
  • Service (28-37)
internal/sms-gateway/handlers/events/mobile.go (2)
  • NewMobileController (19-27)
  • MobileController (13-17)
internal/sms-gateway/handlers/messages/mobile.go (2)
  • NewMobileController (35-43)
  • MobileController (29-33)
internal/sms-gateway/handlers/settings/mobile.go (2)
  • NewMobileController (21-35)
  • MobileController (14-19)
internal/sms-gateway/modules/metrics/module.go (2)
internal/sms-gateway/app.go (1)
  • Module (33-58)
internal/sms-gateway/handlers/module.go (1)
  • Module (15-41)
internal/sms-gateway/modules/push/fcm/client.go (4)
internal/sms-gateway/modules/push/fcm/errors.go (1)
  • ErrInitializationFailed (6-6)
internal/sms-gateway/modules/push/client/types.go (3)
  • Client (9-13)
  • Message (15-18)
  • Event (20-23)
internal/sms-gateway/modules/push/upstream/client.go (1)
  • Client (22-27)
internal/sms-gateway/modules/push/types.go (1)
  • Event (17-17)
internal/sms-gateway/modules/push/fcm/utils.go (2)
internal/sms-gateway/modules/push/client/types.go (1)
  • Event (20-23)
internal/sms-gateway/modules/push/types.go (1)
  • Event (17-17)
internal/config/module.go (8)
internal/sms-gateway/app.go (1)
  • Module (33-58)
internal/sms-gateway/modules/sse/config.go (4)
  • Option (5-5)
  • Config (7-9)
  • NewConfig (19-27)
  • WithKeepAlivePeriod (33-41)
internal/config/config.go (13)
  • Config (10-20)
  • Default (89-127)
  • HTTP (27-33)
  • Database (44-55)
  • Gateway (22-25)
  • API (35-38)
  • GatewayModePublic (6-6)
  • OpenAPI (40-42)
  • Messages (75-78)
  • Tasks (63-65)
  • SSE (71-73)
  • Cache (80-82)
  • PubSub (84-87)
internal/sms-gateway/modules/push/service.go (1)
  • Config (23-30)
internal/worker/config/config.go (3)
  • Config (9-13)
  • Default (34-66)
  • Tasks (15-19)
internal/sms-gateway/modules/messages/config.go (1)
  • Config (5-8)
internal/sms-gateway/pubsub/config.go (1)
  • Config (4-7)
internal/sms-gateway/cache/config.go (1)
  • Config (4-6)
internal/sms-gateway/modules/db/health.go (1)
pkg/health/types.go (2)
  • Checks (67-67)
  • Provider (69-75)
internal/config/config.go (2)
internal/worker/config/config.go (3)
  • Default (34-66)
  • Config (9-13)
  • Tasks (15-19)
internal/sms-gateway/pubsub/config.go (1)
  • Config (4-7)
internal/sms-gateway/handlers/settings/mobile.go (7)
internal/sms-gateway/handlers/base/handler.go (1)
  • Handler (15-18)
internal/sms-gateway/modules/devices/service.go (1)
  • Service (15-24)
internal/sms-gateway/modules/settings/service.go (1)
  • Service (19-25)
internal/sms-gateway/handlers/events/mobile.go (2)
  • NewMobileController (19-27)
  • MobileController (13-17)
internal/sms-gateway/handlers/messages/mobile.go (2)
  • NewMobileController (35-43)
  • MobileController (29-33)
internal/sms-gateway/handlers/webhooks/mobile.go (2)
  • NewMobileController (21-33)
  • MobileController (15-19)
internal/sms-gateway/models/models.go (1)
  • Device (34-45)
internal/sms-gateway/pubsub/pubsub.go (4)
internal/sms-gateway/pubsub/config.go (1)
  • Config (4-7)
pkg/pubsub/options.go (1)
  • WithBufferSize (15-19)
pkg/pubsub/memory.go (1)
  • NewMemory (24-38)
pkg/pubsub/redis.go (2)
  • NewRedis (42-78)
  • RedisConfig (15-27)
internal/sms-gateway/modules/webhooks/models.go (2)
internal/sms-gateway/models/models.go (3)
  • SoftDeletableModel (12-16)
  • User (18-24)
  • Device (34-45)
internal/sms-gateway/models/migration.go (1)
  • Migrate (13-18)
internal/sms-gateway/modules/settings/models.go (2)
internal/sms-gateway/models/models.go (2)
  • TimedModel (7-10)
  • User (18-24)
internal/sms-gateway/models/migration.go (1)
  • Migrate (13-18)
internal/sms-gateway/handlers/messages/3rdparty.go (8)
internal/sms-gateway/handlers/logs/3rdparty.go (2)
  • NewThirdPartyController (24-31)
  • ThirdPartyController (20-22)
internal/sms-gateway/handlers/settings/3rdparty.go (2)
  • NewThirdPartyController (35-44)
  • ThirdPartyController (28-33)
internal/sms-gateway/handlers/base/handler.go (1)
  • Handler (15-18)
internal/sms-gateway/modules/messages/service.go (1)
  • EnqueueOptions (22-24)
internal/sms-gateway/modules/messages/errors.go (2)
  • ValidationError (9-9)
  • ErrNoContent (6-6)
internal/sms-gateway/modules/messages/repository.go (3)
  • ErrMultipleMessagesFound (18-18)
  • ErrMessageNotFound (16-16)
  • ErrMessageAlreadyExists (17-17)
internal/sms-gateway/modules/devices/repository.go (3)
  • ErrNotFound (14-14)
  • ErrInvalidFilter (15-15)
  • ErrMoreThanOne (16-16)
internal/sms-gateway/modules/devices/errors.go (1)
  • ErrInvalidUser (6-6)
internal/sms-gateway/modules/devices/service.go (3)
internal/sms-gateway/models/models.go (1)
  • Device (34-45)
internal/sms-gateway/modules/devices/repository_filter.go (4)
  • SelectFilter (9-9)
  • WithUserID (23-27)
  • WithID (11-15)
  • ActiveWithin (29-33)
internal/sms-gateway/modules/devices/repository.go (1)
  • ErrNotFound (14-14)
internal/sms-gateway/handlers/messages/params.go (1)
internal/sms-gateway/modules/messages/repository_filter.go (3)
  • SelectFilter (16-23)
  • SelectOptions (51-62)
  • Order (7-7)
internal/sms-gateway/modules/events/module.go (3)
internal/sms-gateway/app.go (1)
  • Module (33-58)
internal/sms-gateway/modules/messages/module.go (1)
  • Module (10-25)
internal/sms-gateway/modules/push/module.go (1)
  • Module (11-36)
internal/sms-gateway/modules/push/upstream/client.go (2)
internal/sms-gateway/modules/push/fcm/client.go (2)
  • New (21-27)
  • Client (14-19)
internal/sms-gateway/modules/push/client/types.go (2)
  • Client (9-13)
  • Message (15-18)
internal/sms-gateway/modules/sse/service.go (2)
internal/sms-gateway/modules/sse/errors.go (1)
  • ErrNoConnection (6-6)
internal/sms-gateway/modules/sse/metrics.go (2)
  • ErrorTypeMarshalError (22-22)
  • ErrorTypeWriteFailure (21-21)
internal/sms-gateway/modules/messages/cache.go (2)
internal/sms-gateway/modules/messages/domain.go (1)
  • MessageStateOut (38-44)
pkg/cache/options.go (2)
  • WithTTL (20-31)
  • AndSetTTL (65-69)
internal/sms-gateway/modules/push/service.go (2)
internal/sms-gateway/modules/push/client/types.go (3)
  • Client (9-13)
  • Event (20-23)
  • Message (15-18)
internal/sms-gateway/modules/push/types.go (1)
  • Event (17-17)
internal/sms-gateway/modules/webhooks/errors.go (2)
internal/sms-gateway/pubsub/pubsub.go (1)
  • New (19-51)
internal/sms-gateway/modules/messages/errors.go (1)
  • ValidationError (9-9)
internal/sms-gateway/modules/messages/repository.go (2)
internal/sms-gateway/modules/messages/repository_filter.go (3)
  • SelectFilter (16-23)
  • SelectOptions (51-62)
  • Order (7-7)
internal/sms-gateway/modules/messages/models.go (3)
  • Message (36-56)
  • ProcessingStatePending (17-17)
  • MessageRecipient (139-145)
internal/sms-gateway/modules/devices/repository.go (4)
internal/sms-gateway/handlers/middlewares/deviceauth/deviceauth.go (1)
  • New (20-44)
internal/sms-gateway/models/models.go (1)
  • Device (34-45)
internal/sms-gateway/modules/messages/repository.go (1)
  • Repository (20-22)
internal/sms-gateway/modules/webhooks/repository.go (1)
  • Repository (8-10)
pkg/cache/memory.go (2)
pkg/cache/options.go (3)
  • GetOption (49-49)
  • AndDelete (89-93)
  • Option (6-6)
pkg/cache/cache.go (1)
  • Cache (5-40)
internal/sms-gateway/handlers/mobile.go (1)
internal/sms-gateway/models/models.go (2)
  • Device (34-45)
  • User (18-24)
internal/sms-gateway/modules/webhooks/service.go (1)
internal/sms-gateway/modules/webhooks/errors.go (1)
  • ErrInvalidEvent (9-9)
internal/sms-gateway/handlers/messages/mobile.go (5)
internal/sms-gateway/handlers/events/mobile.go (2)
  • NewMobileController (19-27)
  • MobileController (13-17)
internal/sms-gateway/handlers/settings/mobile.go (2)
  • NewMobileController (21-35)
  • MobileController (14-19)
internal/sms-gateway/handlers/webhooks/mobile.go (2)
  • NewMobileController (21-33)
  • MobileController (15-19)
internal/sms-gateway/handlers/base/handler.go (1)
  • Handler (15-18)
internal/sms-gateway/models/models.go (1)
  • Device (34-45)
internal/sms-gateway/modules/messages/service.go (7)
internal/sms-gateway/modules/messages/repository_filter.go (3)
  • Order (7-7)
  • SelectFilter (16-23)
  • SelectOptions (51-62)
internal/sms-gateway/modules/messages/domain.go (4)
  • MessageOut (25-29)
  • MessageStateIn (31-36)
  • MessageStateOut (38-44)
  • MessageIn (9-23)
internal/sms-gateway/models/models.go (2)
  • Device (34-45)
  • User (18-24)
internal/sms-gateway/modules/messages/models.go (6)
  • MessageState (157-162)
  • ProcessingState (13-13)
  • ProcessingStatePending (17-17)
  • MessageRecipient (139-145)
  • Message (36-56)
  • NewMessage (58-83)
internal/sms-gateway/modules/messages/repository.go (1)
  • ErrMessageNotFound (16-16)
internal/sms-gateway/modules/events/events.go (2)
  • NewMessageEnqueuedEvent (9-11)
  • NewMessagesExportRequestedEvent (17-25)
internal/sms-gateway/modules/messages/errors.go (2)
  • ErrNoContent (6-6)
  • ValidationError (9-9)
internal/sms-gateway/modules/auth/service.go (5)
internal/sms-gateway/online/service.go (2)
  • New (28-37)
  • Service (14-17)
internal/sms-gateway/modules/auth/types.go (1)
  • OneTimeCode (15-18)
internal/sms-gateway/models/models.go (4)
  • User (18-24)
  • NewUser (26-32)
  • Device (34-45)
  • NewDevice (47-53)
pkg/crypto/passwords.go (2)
  • MakeBCryptHash (14-20)
  • CompareBCryptHash (22-27)
internal/sms-gateway/modules/auth/errors.go (1)
  • ErrAuthorizationFailed (6-6)
internal/sms-gateway/modules/messages/models.go (1)
internal/sms-gateway/models/models.go (1)
  • SoftDeletableModel (12-16)
internal/sms-gateway/modules/push/types.go (1)
internal/sms-gateway/modules/push/client/types.go (1)
  • Event (20-23)
internal/sms-gateway/modules/messages/errors.go (1)
internal/sms-gateway/modules/webhooks/errors.go (1)
  • ValidationError (12-16)
🪛 checkmake (0.2.2)
Makefile

[warning] 10-10: Missing required phony target "all"

(minphony)


[warning] 10-10: Missing required phony target "clean"

(minphony)


[warning] 10-10: Missing required phony target "test"

(minphony)


[warning] 15-15: Target "all" should be declared PHONY.

(phonydeclared)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
  • GitHub Check: Benchmark
  • GitHub Check: E2E
  • GitHub Check: Analyze (go)

@capcom6 capcom6 marked this pull request as ready for review November 18, 2025 12:29
@capcom6
Copy link
Member Author

capcom6 commented Nov 18, 2025

Worker deployed...

@capcom6 capcom6 force-pushed the actions/migrate-to-goreleaser branch from bd00981 to 23abfa0 Compare November 20, 2025 06:59
Copy link

@coderabbitai coderabbitai bot left a 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 calls DecrementActiveConnections(). This leaves the active connections metric permanently inflated by the number of open connections at shutdown.

When handleStream goroutines subsequently call the deferred removeConnection(), they won't find their connections in the map (because Close() 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 inner if conn.id == connID check. This means:

  1. If removeConnection is called with a connID that doesn't exist (but deviceID does), the metric is still decremented → incorrect count
  2. If the connection was already removed by Close(), the metric isn't decremented because the deviceID doesn't exist anymore → combined with the Close() issue, this creates a metric leak

Apply 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 inamedparam linter 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.ValidationError

This package’s ValidationError (string type) still collides by name with webhooks.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 v1 with 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, UpstreamPushRequest is 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 ignored nanoid.Standard error

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‑persisted User/Device objects when inserts fail

RegisterUser and RegisterDevice still 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 nil on 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: Move fx.Private before newRepository to make it private.

In uber-go/fx, fx.Private only affects constructors that follow it in the same fx.Provide call. Since newRepository is listed before fx.Private, it remains accessible to the wider dependency graph. If newRepository should 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: Keep newMetrics private by reordering fx.Private.

fx.Private only applies to constructors that follow it in the same fx.Provide call; placing it after newMetrics makes the metrics provider visible to the wider app graph, changing the module’s surface area. To keep newMetrics private to the events module, swap the order:

-		fx.Provide(newMetrics, fx.Private),
+		fx.Provide(fx.Private, newMetrics),

The rest of the Module() wiring (including log.Named("events") and the lifecycle hook) looks good and matches the project’s module patterns. Based on learnings

In 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 errors

The 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

Validate still 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.Time values:

 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 prepareMessage

TTL handling still does an unchecked conversion from user-controlled *message.TTL (uint64) to time.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.Second can overflow int64, leading to incorrect validUntil values. 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’s ValidationError mapping.

🧹 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, nil is non-idiomatic when the intent is "no checks to report." The idiomatic Go approach is to return an empty map (healthmod.Checks{}) rather than nil, which also maintains consistency with ReadyProbe that 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 uses actions/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: 0
internal/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 uses fiber.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.ErrUnauthorized
internal/sms-gateway/modules/push/fcm/client.go (1)

24-25: Consider removing explicit zero-value initializations.

Lines 24-25 explicitly initialize client to nil and mux to sync.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.RWMutex is 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 entirely

Actually, 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: GenerateUserCode logic is fine, but for range 3 depends 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.mod or 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 SetOnline

Using ErrAuthorizationFailed in AuthorizeRegistration and wrapping device lookup errors with it in AuthorizeDevice is a nice improvement over generic errors.

One minor point: in AuthorizeDevice you spawn a goroutine with context.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 invalidation

In 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) the User struct currently exposes ID and PasswordHash only. Combined with GetByLogin doing Where("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 userID with GetByLogin). If there is a separate logical login/username, then:

  • GetByLogin probably shouldn’t filter on "id", and/or
  • ChangePassword should be using GetByID and passing the correct key to usersCache.Delete.

Either way, worth double‑checking that:

  1. GetByLogin is querying the right column.
  2. The key used in usersCache.Set(username, password, *user) matches the key you pass into Delete(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 duplicate name field

The updated messages (“failed to acquire/release lock”) are more descriptive. Note that logger is already scoped with zap.String("name", task.Name()), so passing zap.String("name", task.Name()) again is slightly redundant; you could rely on the scoped logger and only add zap.Error(err) here.

internal/sms-gateway/modules/webhooks/errors.go (1)

18-20: IsValidationError is correct but can follow the canonical errors.As pattern

The new Error() formatting is clearer, and using errors.As makes IsValidationError robust to wrapping. For style and to avoid the extra allocation from new(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 ValidationError usage 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 validation

Wrapping structural mismatches in filterMap and appendMap with %w and ErrInvalidField makes 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 %q for 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/v2 support 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.IntN usage 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 mobileControllerParams with fx.In, while webhooks/mobile.go and settings/mobile.go have 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 uses thirdPartyControllerParams with fx.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 solid

Using a pointer MobileRegisterRequest and returning fiber.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 to fiber.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 semantics

The enqueue path:

  • Prepares a Message via prepareMessage.
  • Builds an initial MessageStateOut using msg.ExtID and recipient states.
  • Inserts the message, caches the persisted state, increments metrics, and asynchronously notifies the device via eventsSvc.Notify.

Returning a non-nil state alongside insErr:

if insErr := s.messages.Insert(msg); insErr != nil {
    return state, insErr
}

is unconventional in Go; callers must be disciplined to ignore state when err != nil. Current 3rd‑party handler drops state on error, but if future call sites use it, this could be surprising. Either documenting this behavior in Enqueue’s comment or switching to return nil, insErr would make the contract clearer.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between bd00981 and 23abfa0.

⛔ Files ignored due to path filters (1)
  • go.sum is 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.go
  • pkg/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.go
  • pkg/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.go
  • internal/sms-gateway/modules/events/module.go
  • internal/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.go
  • internal/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.go
  • internal/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)

Copy link

@coderabbitai coderabbitai bot left a 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.RWMutex is ready to use in Go. The explicit initialization sync.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

📥 Commits

Reviewing files that changed from the base of the PR and between 23abfa0 and fda176d.

📒 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 eventsBufferSize constant 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 handleStream when a write fails. This ensures the deferred removeConnection runs 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 handleStream improves 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) and handleStream (connection lifecycle) is well-designed.

@capcom6
Copy link
Member Author

capcom6 commented Nov 20, 2025

Worker deployed...

@capcom6
Copy link
Member Author

capcom6 commented Nov 23, 2025

Deployed...

@capcom6 capcom6 added the deployed The PR is deployed on staging label Nov 23, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

deployed The PR is deployed on staging

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants