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 title updated to "SMSGate API".
  • Improvements

    • More reliable SSE streaming, push delivery and message enqueue/export flows.
    • Hardened webhooks and settings with stronger validation and ownership checks.
    • Enhanced message retrieval: ordering, pagination and selection logic; improved device selection.
  • Bug Fixes

    • Standardized clearer user-facing error messages and consistent HTTP validation responses.
  • Chores

    • Updated release/packaging and developer tooling (build/release scripts, CI and Makefile).

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

@coderabbitai
Copy link

coderabbitai bot commented Nov 4, 2025

Warning

Rate limit exceeded

@capcom6 has exceeded the limit for the number of commits or files that can be reviewed per hour. Please wait 12 minutes and 19 seconds before requesting another review.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

📥 Commits

Reviewing files that changed from the base of the PR and between 0acb43d and 71fb64f.

⛔ Files ignored due to path filters (1)
  • go.sum is excluded by !**/*.sum
📒 Files selected for processing (92)
  • api/requests.http (0 hunks)
  • cmd/sms-gateway/main.go (2 hunks)
  • go.mod (0 hunks)
  • internal/config/config.go (3 hunks)
  • internal/config/module.go (1 hunks)
  • internal/sms-gateway/app.go (2 hunks)
  • internal/sms-gateway/cache/errors.go (1 hunks)
  • internal/sms-gateway/cache/factory.go (2 hunks)
  • internal/sms-gateway/handlers/base/handler.go (1 hunks)
  • internal/sms-gateway/handlers/base/handler_test.go (5 hunks)
  • internal/sms-gateway/handlers/converters/messages.go (1 hunks)
  • internal/sms-gateway/handlers/devices/3rdparty.go (4 hunks)
  • internal/sms-gateway/handlers/events/mobile.go (1 hunks)
  • internal/sms-gateway/handlers/health.go (2 hunks)
  • internal/sms-gateway/handlers/logs/3rdparty.go (2 hunks)
  • internal/sms-gateway/handlers/messages/3rdparty.go (8 hunks)
  • internal/sms-gateway/handlers/messages/mobile.go (3 hunks)
  • internal/sms-gateway/handlers/messages/params.go (3 hunks)
  • internal/sms-gateway/handlers/middlewares/userauth/userauth.go (2 hunks)
  • internal/sms-gateway/handlers/mobile.go (8 hunks)
  • internal/sms-gateway/handlers/module.go (1 hunks)
  • internal/sms-gateway/handlers/root.go (1 hunks)
  • internal/sms-gateway/handlers/settings/3rdparty.go (4 hunks)
  • internal/sms-gateway/handlers/settings/mobile.go (2 hunks)
  • internal/sms-gateway/handlers/upstream.go (4 hunks)
  • internal/sms-gateway/handlers/webhooks/3rdparty.go (4 hunks)
  • internal/sms-gateway/handlers/webhooks/mobile.go (2 hunks)
  • internal/sms-gateway/models/migration.go (2 hunks)
  • internal/sms-gateway/models/models.go (2 hunks)
  • internal/sms-gateway/models/module.go (1 hunks)
  • internal/sms-gateway/modules/db/health.go (3 hunks)
  • internal/sms-gateway/modules/db/module.go (2 hunks)
  • internal/sms-gateway/modules/devices/repository.go (5 hunks)
  • internal/sms-gateway/modules/devices/repository_filter.go (1 hunks)
  • internal/sms-gateway/modules/devices/service.go (6 hunks)
  • internal/sms-gateway/modules/events/errors.go (1 hunks)
  • internal/sms-gateway/modules/events/metrics.go (3 hunks)
  • internal/sms-gateway/modules/events/module.go (1 hunks)
  • internal/sms-gateway/modules/events/service.go (7 hunks)
  • internal/sms-gateway/modules/events/types.go (2 hunks)
  • internal/sms-gateway/modules/messages/cache.go (2 hunks)
  • internal/sms-gateway/modules/messages/converters.go (1 hunks)
  • internal/sms-gateway/modules/messages/errors.go (1 hunks)
  • internal/sms-gateway/modules/messages/models.go (7 hunks)
  • internal/sms-gateway/modules/messages/module.go (1 hunks)
  • internal/sms-gateway/modules/messages/repository.go (7 hunks)
  • internal/sms-gateway/modules/messages/repository_filter.go (2 hunks)
  • internal/sms-gateway/modules/messages/service.go (7 hunks)
  • internal/sms-gateway/modules/messages/service_test.go (0 hunks)
  • internal/sms-gateway/modules/messages/workers.go (5 hunks)
  • internal/sms-gateway/modules/metrics/handler.go (1 hunks)
  • internal/sms-gateway/modules/metrics/module.go (1 hunks)
  • internal/sms-gateway/modules/push/client.go (1 hunks)
  • internal/sms-gateway/modules/push/client/types.go (1 hunks)
  • internal/sms-gateway/modules/push/fcm/client.go (4 hunks)
  • internal/sms-gateway/modules/push/fcm/errors.go (1 hunks)
  • internal/sms-gateway/modules/push/fcm/utils.go (1 hunks)
  • internal/sms-gateway/modules/push/module.go (2 hunks)
  • internal/sms-gateway/modules/push/service.go (10 hunks)
  • internal/sms-gateway/modules/push/types.go (3 hunks)
  • internal/sms-gateway/modules/push/upstream/client.go (4 hunks)
  • internal/sms-gateway/modules/settings/models.go (1 hunks)
  • internal/sms-gateway/modules/settings/module.go (1 hunks)
  • internal/sms-gateway/modules/settings/repository.go (5 hunks)
  • internal/sms-gateway/modules/settings/service.go (3 hunks)
  • internal/sms-gateway/modules/settings/utils.go (4 hunks)
  • internal/sms-gateway/modules/sse/config.go (2 hunks)
  • internal/sms-gateway/modules/sse/errors.go (1 hunks)
  • internal/sms-gateway/modules/sse/metrics.go (3 hunks)
  • internal/sms-gateway/modules/sse/module.go (1 hunks)
  • internal/sms-gateway/modules/sse/service.go (8 hunks)
  • internal/sms-gateway/modules/webhooks/errors.go (3 hunks)
  • internal/sms-gateway/modules/webhooks/models.go (1 hunks)
  • internal/sms-gateway/modules/webhooks/module.go (1 hunks)
  • internal/sms-gateway/modules/webhooks/repository.go (2 hunks)
  • internal/sms-gateway/modules/webhooks/repository_filter.go (1 hunks)
  • internal/sms-gateway/modules/webhooks/service.go (5 hunks)
  • internal/sms-gateway/online/metrics.go (5 hunks)
  • internal/sms-gateway/online/service.go (5 hunks)
  • internal/sms-gateway/openapi/docs.go (6 hunks)
  • internal/sms-gateway/pubsub/module.go (2 hunks)
  • internal/sms-gateway/pubsub/pubsub.go (2 hunks)
  • internal/version/version.go (1 hunks)
  • internal/worker/app.go (1 hunks)
  • internal/worker/config/config.go (1 hunks)
  • internal/worker/config/types.go (2 hunks)
  • internal/worker/executor/metrics.go (1 hunks)
  • internal/worker/executor/service.go (3 hunks)
  • internal/worker/locker/mysql.go (4 hunks)
  • test/e2e/mobile_test.go (1 hunks)
  • test/e2e/utils_test.go (1 hunks)
  • test/e2e/webhooks_test.go (1 hunks)

Walkthrough

Removed legacy Docker build/publish workflows and added Goreleaser-driven CI; introduced many API and implementation refactors (Module vars → Module() functions), push client abstraction, exported cache/pubsub types, messages/auth signature changes, new constructors and sentinel errors, model/migration updates, and numerous lint/style adjustments.

Changes

Cohort / File(s) Summary
CI & release
/.github/workflows/..., .goreleaser.yaml, Dockerfile.goreleaser, Makefile
Deleted legacy docker-build.yml/docker-publish.yml; added pr.yml; updated release.yml to use Goreleaser; added .goreleaser.yaml and Dockerfile.goreleaser; expanded Makefile targets.
Linters & repo config
/.golangci.yml, /.gitignore
Added comprehensive 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
Replaced exported package-level Module variables with func Module() fx.Option factories across many packages, preserving internal wiring.
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 use the interface and lifecycle hooks.
Cache & PubSub public API
pkg/cache/*.go, pkg/pubsub/*.go, internal/sms-gateway/pubsub/*
Exported/renamed types (MemoryCache, RedisCache, MemoryPubSub, RedisPubSub), changed constructors/receivers, altered options.apply signatures, added sentinel errors (ErrInvalidConfig, ErrInvalidScheme), and standardized error wrapping.
Messages domain & handlers
internal/sms-gateway/modules/messages/*, internal/sms-gateway/handlers/messages/*
Renamed domain types (MessagesOrderOrder), added SelectFilter/SelectOptions builders, introduced constructors/helpers, changed repository/service/handler signatures (Enqueue/GetState/SelectPending/UpdateState), and updated models/migrations.
Auth subsystem
internal/sms-gateway/modules/auth/*
Renamed AuthCodeOneTimeCode; repo/service methods now use pointer returns; added ErrAuthorizationFailed, internal usersCache, Service.Run(ctx) and adjusted auth flows/error wrapping.
SSE & events
internal/sms-gateway/modules/sse/*, internal/sms-gateway/modules/events/*
Added ErrNoConnection and ErrValidationFailed; refactored SSE to per-connection handlers, introduced buffer-size constant, improved metrics labeling, and hardened event (de)serialization and error handling.
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 from handlers, added devices.GetAny, and 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, adjusted thresholds and probe signatures, made NewHealth unexported, and extended 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 SoftDeletableModel/TimedModel embeddings, constructors (NewUser, NewDevice, NewDeviceSettings), switched AutoMigrate to new(Type) usage and wrapped migration errors.
Handlers & middleware
internal/sms-gateway/handlers/*, internal/sms-gateway/handlers/middlewares/userauth/*, internal/sms-gateway/app.go
Replaced fx.In-based constructors with explicit constructors (services/logger/validator), changed GetUser to return *models.User, adjusted handler signatures, and updated app wiring to call Module().
Tests & benchmarks
pkg/cache/*_test.go, pkg/cache/*_bench*.go, test/e2e/*, internal/sms-gateway/modules/messages/service_test.go
Modernized tests (use errors.Is, context.Background, range loops), added e2e webhook tests, reformatted signatures, and removed a unit test file.
Misc errors & small edits
assorted internal/**, pkg/**
Normalized error phrasing ("can't"→"failed"), added sentinel errors, wrapped errors with %w, added nolint directives, and made many small comment/formatting changes across files.

Sequence Diagram(s)

sequenceDiagram
    actor Dev as Developer
    participant CI as CI (Goreleaser)
    participant FX as FX App
    participant ModuleFactory as Module()
    participant newClient as newClient()
    participant Client as client.Client
    participant PushSvc as Push Service
    participant Messages as Messages Service

    Note right of CI `#FFF3DE`: CI/release now uses Goreleaser
    Dev->>CI: push tag / open PR
    CI-->>Dev: build/publish artifacts

    Note right of ModuleFactory `#DFF1DF`: Module() factories replace package vars
    FX->>ModuleFactory: invoke Module()
    ModuleFactory-->>FX: provide configs, services, client factory

    Note right of newClient `#E8F4FF`: push client factory returns concrete client
    FX->>newClient: provide push.Config
    newClient-->>Client: return concrete client or error

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

Estimated code review effort

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

Focus review areas:

  • Module var → Module() conversions: callers, fx.Provide/As types, init/migration side-effects.
  • Push client abstraction and lifecycle: client.Client interface, newClient factory, fcm/upstream adapters, Open/Send/Close semantics.
  • Messages and auth API changes: repository/service signature changes, SelectFilter/SelectOptions behavior, Enqueue/GetState/UpdateState flows, migrations.
  • Cache & PubSub renames: exported type renames and options.apply signature changes (consumer compatibility).
  • DB migrations/models and AutoMigrate changes.
  • Tests removed/changed and CI workflow replacement.

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.08% 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' directly and specifically summarizes the main change: migrating the build process 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
@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
@capcom6 capcom6 force-pushed the actions/migrate-to-goreleaser branch from fda176d to 8bbe45b Compare November 24, 2025 23:14
@capcom6 capcom6 removed the deployed The PR is deployed on staging label Nov 24, 2025
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 (8)
internal/sms-gateway/modules/push/upstream/client.go (1)

62-62: Remove the unnecessary type conversion.

This was already flagged in a previous review. UpstreamPushRequest is a type alias for []smsgateway.PushNotification, so the explicit conversion is redundant.

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

275-276: Handle or document the ignored error from nanoid.Standard.

The error from nanoid.Standard(idGenSize) is still discarded. While failures are unlikely with a constant size of 21, silently ignoring initialization errors can make debugging painful if something does go wrong. Either handle it or explain why it’s safe to ignore:

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

or:

 	const idGenSize = 21
-	idGen, _ := nanoid.Standard(idGenSize)
+	// nanoid.Standard with size 21 is guaranteed not to error.
+	idGen, _ := nanoid.Standard(idGenSize)
internal/sms-gateway/handlers/messages/params.go (1)

24-27: Fix date-range validation to compare parsed timestamps, not strings.

p.StartDate > p.EndDate is a lexicographical comparison, so valid RFC3339 timestamps with different offsets can be rejected (e.g. "2025-11-10T12:00:00+02:00" vs "2025-11-10T11:00:00+00:00"). Parse both values to time.Time 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
-	}
-
-	return nil
-}
+func (p *thirdPartyGetQueryParams) Validate() error {
+	if p.StartDate != "" && p.EndDate != "" {
+		from, err := time.Parse(time.RFC3339, p.StartDate)
+		if err != nil {
+			return errors.New("`from` date must be before `to` date") //nolint:err113 // won't be used directly
+		}
+
+		to, err := time.Parse(time.RFC3339, p.EndDate)
+		if err != nil {
+			return errors.New("`from` date must be before `to` date") //nolint:err113 // won't be used directly
+		}
+
+		if from.After(to) {
+			return errors.New("`from` date must be before `to` date") //nolint:err113 // won't be used directly
+		}
+	}
+
+	return nil
+}
internal/sms-gateway/modules/messages/errors.go (1)

3-13: Consider renaming ValidationError to avoid cross‑package ambiguity.

There is also a webhooks.ValidationError type (struct) in internal/sms-gateway/modules/webhooks/errors.go. Having two exported ValidationError types in different packages is legal but can be confusing where both packages are imported together. A more specific name like MessageValidationError would make call sites clearer, but this is purely a naming/ergonomics concern, not a functional bug.

internal/config/module.go (2)

88-109: Fix path defaulting logic to check Path instead of Host.

The condition on line 90 checks cfg.HTTP.API.Host == "" but sets the Path. This means if a user explicitly sets a custom Path but leaves Host empty, their Path will be overwritten with /api. The check should verify if Path is empty.

Apply this diff:

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

26-37: fx.Private must be wrapped with fx.Annotate.

Passing fx.Private as a separate argument to fx.Provide is incorrect—Fx will treat it as a constructor and fail at startup. You need to wrap the provider function with fx.Annotate.

Apply this diff:

 		fx.Provide(
-			func(log *zap.Logger) Config {
-				defaultConfig := Default()
-
-				if err := config.LoadConfig(&defaultConfig); err != nil {
-					log.Error("Error loading config", zap.Error(err))
-				}
-
-				return defaultConfig
-			},
-			fx.Private,
+			fx.Annotate(
+				func(log *zap.Logger) Config {
+					defaultConfig := Default()
+
+					if err := config.LoadConfig(&defaultConfig); err != nil {
+						log.Error("Error loading config", zap.Error(err))
+					}
+
+					return defaultConfig
+				},
+				fx.Private,
+			),
 		),
internal/sms-gateway/modules/messages/service.go (2)

176-218: Enqueue: initial state + cache are correct; reconsider returning non‑nil state on insert error.

prepareMessage, caching under msg.ExtID (fixing the earlier cache‑key issue), and firing eventsSvc.Notify in a goroutine all look good and align with the existing async notification pattern (based on learnings). The only oddity is returning a non‑nil state when Insert fails:

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

This diverges from typical Go (nil, err) conventions and makes it easier for future callers to misuse the partial state if they ever forget to check err first or are unaware of this contract.

If you don’t rely on the partial state on failure, I’d simplify to:

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

Otherwise, consider adding a brief godoc comment for Enqueue that explicitly documents that a partially populated state may be returned alongside a non‑nil error.


220-272: prepareMessage: content handling is solid; TTL gosec suppression still needs an explicit justification or bound.

The per‑row phone validation (with row index in the error), NewMessage construction, and content switch (TextContent vs DataContent) with ErrNoContent as a sentinel all look good and address the earlier dynamic‑error lint.

The remaining concern is the TTL handling:

if message.TTL != nil && *message.TTL > 0 {
    //nolint:gosec // not a problem
    validUntil = anys.AsPointer(
        time.Now().Add(time.Duration(*message.TTL) * time.Second),
    )
}

If TTL is user‑controlled and not type‑ or range‑constrained elsewhere, this unchecked conversion is exactly what gosec warns about (potential time.Duration overflow on very large TTLs). At minimum, either:

  • Add an explicit upper bound on TTL here, or
  • Expand the comment to point to the upstream validation or type constraints that make the suppression safe (e.g., if TTL is a smaller integer type whose max cannot overflow time.Duration).

For example, one possible pattern (tune the constant and error type as appropriate):

 if message.TTL != nil && *message.TTL > 0 {
-    //nolint:gosec // not a problem
-    validUntil = anys.AsPointer(
-        time.Now().Add(time.Duration(*message.TTL) * time.Second),
-    )
+    const maxTTLSeconds = 315360000 // example: 10 years
+    if *message.TTL > maxTTLSeconds {
+        return nil, ValidationError("ttl exceeds maximum allowed value")
+    }
+    validUntil = anys.AsPointer(
+        time.Now().Add(time.Duration(*message.TTL) * time.Second),
+    )
 }

If you already have stronger guarantees on TTL elsewhere, surfacing that context in the comment would help future readers understand why the suppression is safe.

🧹 Nitpick comments (15)
internal/sms-gateway/modules/db/health.go (1)

20-21: Optional: Remove redundant explicit initialization.

The explicit initialization failedPings: atomic.Int64{} is redundant since atomic.Int64 already has a zero value. You can safely omit this field from the struct literal.

Apply this diff to simplify:

 func newHealth(db *sql.DB) *health {
 	return &health{
 		db: db,
-
-		failedPings: atomic.Int64{},
 	}
 }
internal/sms-gateway/modules/push/upstream/client.go (1)

18-20: Hardcoded API base URL.

The baseURL is hardcoded. Consider making this configurable via the options map to support different environments (staging, testing, self-hosted instances).

internal/sms-gateway/pubsub/module.go (1)

20-22: Empty OnStart hook is unnecessary.

The fx.Hook struct doesn't require both OnStart and OnStop to be defined. This no-op hook can be removed to reduce noise.

 			lc.Append(fx.Hook{
-				OnStart: func(_ context.Context) error {
-					return nil
-				},
 				OnStop: func(_ context.Context) error {
internal/sms-gateway/modules/settings/models.go (1)

11-17: DeviceSettings embedding and constructor look good; consider minor optional tweaks

  • Embedding models.TimedModel and switching Migrate to use new(DeviceSettings) are consistent with the rest of the codebase and GORM usage.
  • The NewDeviceSettings constructor is a nice centralization point; if you ever want to guarantee Settings is never nil on write paths, you could normalize it there (e.g., treat nil as an empty map) and remove a few downstream nil-guards.
  • If you run linters that enforce GoDoc on exported symbols, consider adding a short comment for NewDeviceSettings to avoid warnings.

Also applies to: 19-24, 28-31

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

16-23: GetSettings UserID initialization is a defensive improvement, not a required fix

The concern about empty UserID on not-found reads is well-intentioned, but verification shows this is not a practical issue: GetSettings results are not reused for writes. The service layer extracts only the Settings map and discards the struct. When updates occur, UpdateSettings creates a fresh DeviceSettings via NewDeviceSettings(userID, ...), so the empty UserID from a read-miss never reaches write operations.

That said, the suggestion to initialize UserID upfront is still worthwhile—it clarifies intent and makes the struct state predictable should the code evolve. The proposed change is defensible as a refactor.

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

29-29: Optional: Redundant explicit initialization.

The explicit sync.Mutex{} initialization is unnecessary since the zero value of sync.Mutex is ready to use. You can simplify to just declaring the field without explicit initialization in the struct literal.

-		mu:    sync.Mutex{},
+		mu:    sync.Mutex{}, // zero value is usable; explicit init optional

Or remove it from the literal entirely (it will be zero-initialized automatically):

-		mu:    sync.Mutex{},
 		conns: make(map[string]*sql.Conn),
internal/sms-gateway/handlers/settings/3rdparty.go (1)

85-85: Optional: Consider retaining capacity hint.

The previous make(map[string]any, 8) capacity hint could reduce allocations if settings typically contain around 8 keys. For non-critical paths this is fine, but if you know the typical size, a capacity hint can be a small optimization.

Also applies to: 119-119

internal/sms-gateway/handlers/messages/params.go (1)

58-76: Ensure min usage matches your Go/toolchain version.

ToOptions uses min(p.Limit, maxLimit). If you’re not on Go 1.21+ (where min is a language builtin), this will not compile unless there’s a local min helper in this package. If the project targets older Go, add a small helper instead:

+func min(a, b int) int {
+	if a < b {
+		return a
+	}
+	return b
+}

Also applies to: 79-87

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

95-136: Consider using fiber.NewError for internal failures in postDevice.

For RegisterUser / RegisterDevice failures you currently return fmt.Errorf("failed to …: %w", err), while other handlers in this file wrap errors as fiber.NewError with explicit status codes. For consistency and clearer HTTP semantics, consider:

-		user, err = h.authSvc.RegisterUser(login, password)
-		if err != nil {
-			return fmt.Errorf("failed to create user: %w", err)
-		}
+		user, err = h.authSvc.RegisterUser(login, password)
+		if err != nil {
+			return fiber.NewError(fiber.StatusInternalServerError, "failed to create user")
+		}-	device, err := h.authSvc.RegisterDevice(user, req.Name, req.PushToken)
-	if err != nil {
-		return fmt.Errorf("failed to register device: %w", err)
-	}
+	device, err := h.authSvc.RegisterDevice(user, req.Name, req.PushToken)
+	if err != nil {
+		return fiber.NewError(fiber.StatusInternalServerError, "failed to register device")
+	}

This keeps client-visible behavior aligned with patchDevice, getUserCode, and changePassword.

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

7-7: GetAny logic looks correct; consider API consistency and Go version for math/rand/v2.

  • GetAny builds filters correctly (user ID, optional device ID, optional ActiveWithin) and handles 0 / 1 / many results sensibly. The rand.IntN(len(devices)) call is safe because it only runs when len(devices) > 1.
  • This method returns *models.Device while Get returns models.Device by value. If you don’t need in‑place mutation by callers, you might consider returning a value here too for API consistency across the service.
  • Importing math/rand/v2 is fine for non‑critical randomness, but it does assume a recent enough Go toolchain; please double‑check that your go version in go.mod (and CI) supports this package.

The logging and temporary‑error variable refactors (setErr, cacheErr, rmErr) are clear and non‑disruptive.

Also applies to: 80-107, 121-123, 131-134, 156-160, 180-184, 188-190

internal/config/module.go (1)

118-122: TODO: Make UnusedLifetime configurable.

There's a TODO to make the UnusedLifetime value configurable instead of hardcoding it to 365 days.

Would you like me to open an issue to track this task, or help generate a configuration option for this value?

internal/config/config.go (1)

63-69: Consider adding deprecation notice to the environment variable documentation.

The Tasks.Hashing section is marked as deprecated in the struct tag comment. Ensure this deprecation is also documented in any external configuration documentation or README to help users migrate to the new Messages.HashingIntervalSeconds field.

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

20-24: Consider removing the empty field.

The empty Message field is only used as a placeholder for Delete operations. Using (*Message)(nil) or &Message{} directly in the Cleanup method would be more idiomatic and avoid storing unnecessary state in the repository struct.

 type Repository struct {
 	db *gorm.DB
-
-	empty Message
 }

And in Cleanup:

-		Delete(&r.empty)
+		Delete((*Message)(nil))

105-112: Builder pattern usage is unconventional.

The *new(SelectFilter).WithDeviceID(...).WithState(...) pattern works but is unusual. If the With* methods return pointer receivers, consider starting with a zero value directly:

 func (r *Repository) SelectPending(deviceID string, order Order) ([]Message, error) {
+	filter := SelectFilter{}
+	options := SelectOptions{}
 	messages, _, err := r.Select(
-		*new(SelectFilter).WithDeviceID(deviceID).WithState(ProcessingStatePending),
-		*new(SelectOptions).IncludeRecipients().WithLimit(maxPendingBatch).WithOrderBy(order),
+		*filter.WithDeviceID(deviceID).WithState(ProcessingStatePending),
+		*options.IncludeRecipients().WithLimit(maxPendingBatch).WithOrderBy(order),
 	)
 
 	return messages, err
 }

Alternatively, if methods return values (not pointers), use the struct literal directly without dereferencing.

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

341-357: cleanPhoneNumber: using ValidationError improves error signalling; region could be configurable later.

Switching to ValidationError for parse/validity/type failures while still returning E.164‑formatted numbers on success should make it easier for callers to distinguish user input problems from other error types. If you ever need multi‑region support, consider making the "RU" region configurable (e.g., via Config) instead of hard‑coded here.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between fda176d and 8bbe45b.

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

Applied to files:

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

Applied to files:

  • internal/sms-gateway/modules/sse/service.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 (37)
internal/sms-gateway/modules/push/fcm/utils.go (2)
internal/sms-gateway/modules/push/client/types.go (1)
  • Event (20-23)
internal/sms-gateway/modules/push/types.go (1)
  • Event (17-17)
internal/config/module.go (2)
internal/config/config.go (11)
  • Config (10-20)
  • Default (89-127)
  • HTTP (27-33)
  • Database (44-55)
  • Gateway (22-25)
  • GatewayModePublic (6-6)
  • Messages (75-78)
  • Tasks (63-65)
  • SSE (71-73)
  • Cache (80-82)
  • PubSub (84-87)
internal/worker/config/config.go (3)
  • Config (9-13)
  • Default (34-66)
  • Tasks (15-19)
internal/sms-gateway/models/migration.go (1)
internal/sms-gateway/models/models.go (2)
  • User (18-24)
  • Device (34-45)
internal/worker/locker/mysql.go (1)
internal/worker/locker/locker.go (1)
  • ErrLockNotAcquired (9-9)
internal/sms-gateway/app.go (2)
internal/config/module.go (1)
  • Module (23-140)
internal/worker/app.go (1)
  • Run (19-28)
internal/sms-gateway/modules/sse/module.go (6)
internal/sms-gateway/app.go (1)
  • Module (33-58)
internal/sms-gateway/modules/events/module.go (1)
  • Module (10-39)
internal/sms-gateway/modules/metrics/module.go (1)
  • Module (9-19)
internal/sms-gateway/modules/push/module.go (1)
  • Module (11-36)
internal/sms-gateway/modules/settings/module.go (1)
  • Module (9-23)
internal/sms-gateway/modules/webhooks/module.go (1)
  • Module (9-20)
internal/worker/executor/service.go (1)
internal/worker/config/types.go (1)
  • Duration (11-11)
internal/sms-gateway/handlers/settings/mobile.go (4)
internal/sms-gateway/handlers/base/handler.go (1)
  • Handler (15-18)
internal/sms-gateway/handlers/events/mobile.go (2)
  • NewMobileController (19-27)
  • MobileController (13-17)
internal/sms-gateway/handlers/messages/mobile.go (2)
  • NewMobileController (35-43)
  • MobileController (29-33)
internal/sms-gateway/handlers/webhooks/mobile.go (2)
  • NewMobileController (21-33)
  • MobileController (15-19)
internal/sms-gateway/modules/push/fcm/client.go (4)
internal/sms-gateway/modules/push/fcm/errors.go (1)
  • ErrInitializationFailed (6-6)
internal/sms-gateway/modules/push/client/types.go (3)
  • Client (9-13)
  • Message (15-18)
  • Event (20-23)
internal/sms-gateway/modules/push/upstream/client.go (1)
  • Client (22-27)
internal/sms-gateway/modules/push/types.go (1)
  • Event (17-17)
internal/sms-gateway/modules/sse/config.go (3)
internal/config/config.go (1)
  • Config (10-20)
internal/sms-gateway/modules/push/service.go (1)
  • Config (23-30)
internal/worker/config/types.go (1)
  • Duration (11-11)
internal/sms-gateway/handlers/health.go (1)
pkg/health/types.go (1)
  • CheckDetail (54-64)
internal/sms-gateway/handlers/devices/3rdparty.go (4)
internal/sms-gateway/handlers/base/handler.go (1)
  • Handler (15-18)
internal/sms-gateway/handlers/logs/3rdparty.go (2)
  • NewThirdPartyController (24-31)
  • ThirdPartyController (20-22)
internal/sms-gateway/handlers/messages/3rdparty.go (2)
  • NewThirdPartyController (44-53)
  • ThirdPartyController (37-42)
internal/sms-gateway/handlers/webhooks/3rdparty.go (2)
  • NewThirdPartyController (32-40)
  • ThirdPartyController (26-30)
internal/sms-gateway/modules/sse/service.go (2)
internal/sms-gateway/modules/sse/errors.go (1)
  • ErrNoConnection (6-6)
internal/sms-gateway/modules/sse/metrics.go (2)
  • ErrorTypeMarshalError (22-22)
  • ErrorTypeWriteFailure (21-21)
internal/sms-gateway/handlers/webhooks/3rdparty.go (5)
internal/sms-gateway/handlers/logs/3rdparty.go (2)
  • NewThirdPartyController (24-31)
  • ThirdPartyController (20-22)
internal/sms-gateway/handlers/messages/3rdparty.go (2)
  • NewThirdPartyController (44-53)
  • ThirdPartyController (37-42)
internal/sms-gateway/handlers/base/handler.go (1)
  • Handler (15-18)
internal/sms-gateway/modules/webhooks/errors.go (1)
  • IsValidationError (34-36)
internal/sms-gateway/modules/webhooks/repository_filter.go (1)
  • WithExtID (7-11)
internal/sms-gateway/modules/messages/errors.go (1)
internal/sms-gateway/modules/webhooks/errors.go (1)
  • ValidationError (12-16)
internal/sms-gateway/modules/devices/service.go (3)
internal/sms-gateway/models/models.go (1)
  • Device (34-45)
internal/sms-gateway/modules/devices/repository_filter.go (4)
  • SelectFilter (9-9)
  • WithUserID (23-27)
  • WithID (11-15)
  • ActiveWithin (29-33)
internal/sms-gateway/modules/devices/repository.go (1)
  • ErrNotFound (14-14)
internal/sms-gateway/pubsub/pubsub.go (4)
internal/config/config.go (2)
  • Config (10-20)
  • PubSub (84-87)
internal/sms-gateway/pubsub/config.go (1)
  • Config (4-7)
pkg/pubsub/options.go (1)
  • WithBufferSize (15-19)
pkg/pubsub/redis.go (2)
  • NewRedis (42-78)
  • RedisConfig (15-27)
internal/sms-gateway/cache/factory.go (3)
internal/sms-gateway/cache/errors.go (1)
  • ErrInvalidConfig (6-6)
pkg/cache/errors.go (1)
  • ErrInvalidConfig (7-7)
internal/config/config.go (1)
  • Cache (80-82)
internal/sms-gateway/modules/db/health.go (1)
pkg/health/types.go (2)
  • Checks (67-67)
  • Provider (69-75)
internal/sms-gateway/modules/push/types.go (3)
internal/sms-gateway/modules/events/types.go (1)
  • Event (10-13)
internal/sms-gateway/modules/push/client/types.go (1)
  • Event (20-23)
internal/sms-gateway/modules/sse/types.go (1)
  • Event (7-10)
internal/sms-gateway/handlers/mobile.go (1)
internal/sms-gateway/models/models.go (2)
  • Device (34-45)
  • User (18-24)
internal/sms-gateway/modules/devices/repository.go (3)
internal/sms-gateway/models/models.go (1)
  • Device (34-45)
internal/sms-gateway/modules/messages/repository.go (1)
  • Repository (20-24)
internal/sms-gateway/modules/webhooks/repository.go (1)
  • Repository (8-12)
internal/sms-gateway/modules/settings/models.go (4)
internal/sms-gateway/models/models.go (2)
  • TimedModel (7-10)
  • User (18-24)
internal/sms-gateway/models/migration.go (1)
  • Migrate (13-18)
internal/sms-gateway/modules/messages/models.go (1)
  • Migrate (164-169)
internal/sms-gateway/modules/webhooks/models.go (1)
  • Migrate (38-43)
internal/sms-gateway/modules/webhooks/service.go (1)
internal/sms-gateway/modules/webhooks/errors.go (1)
  • ErrInvalidEvent (9-9)
internal/sms-gateway/modules/messages/cache.go (2)
internal/sms-gateway/modules/messages/domain.go (1)
  • MessageStateOut (38-44)
pkg/cache/options.go (2)
  • WithTTL (20-31)
  • AndSetTTL (65-69)
internal/sms-gateway/handlers/webhooks/mobile.go (4)
internal/sms-gateway/handlers/base/handler.go (1)
  • Handler (15-18)
internal/sms-gateway/modules/webhooks/service.go (1)
  • Service (28-37)
internal/sms-gateway/handlers/events/mobile.go (2)
  • NewMobileController (19-27)
  • MobileController (13-17)
internal/sms-gateway/handlers/messages/mobile.go (2)
  • NewMobileController (35-43)
  • MobileController (29-33)
internal/sms-gateway/modules/push/service.go (2)
internal/sms-gateway/modules/push/client/types.go (3)
  • Client (9-13)
  • Event (20-23)
  • Message (15-18)
internal/sms-gateway/modules/push/types.go (1)
  • Event (17-17)
internal/sms-gateway/handlers/middlewares/userauth/userauth.go (1)
internal/sms-gateway/models/models.go (1)
  • User (18-24)
internal/sms-gateway/modules/messages/repository.go (2)
internal/sms-gateway/modules/messages/models.go (2)
  • Message (36-56)
  • MessageRecipient (139-145)
internal/sms-gateway/modules/messages/repository_filter.go (3)
  • SelectFilter (16-23)
  • SelectOptions (51-62)
  • Order (7-7)
internal/sms-gateway/handlers/events/mobile.go (1)
internal/sms-gateway/models/models.go (1)
  • Device (34-45)
internal/sms-gateway/modules/events/service.go (5)
internal/sms-gateway/pubsub/pubsub.go (1)
  • PubSub (15-15)
internal/sms-gateway/modules/events/errors.go (1)
  • ErrValidationFailed (6-6)
internal/sms-gateway/modules/events/types.go (1)
  • Event (10-13)
internal/sms-gateway/modules/push/client/types.go (1)
  • Event (20-23)
internal/sms-gateway/modules/sse/types.go (1)
  • Event (7-10)
internal/sms-gateway/modules/metrics/module.go (8)
internal/sms-gateway/app.go (1)
  • Module (33-58)
internal/sms-gateway/handlers/module.go (1)
  • Module (15-41)
internal/sms-gateway/modules/db/module.go (1)
  • Module (16-26)
internal/sms-gateway/modules/events/module.go (1)
  • Module (10-39)
internal/sms-gateway/modules/messages/module.go (1)
  • Module (10-25)
internal/sms-gateway/modules/settings/module.go (1)
  • Module (9-23)
internal/sms-gateway/modules/sse/module.go (1)
  • Module (10-32)
internal/sms-gateway/modules/webhooks/module.go (1)
  • Module (9-20)
internal/sms-gateway/modules/messages/converters.go (1)
internal/sms-gateway/modules/messages/domain.go (1)
  • MessageOut (25-29)
internal/sms-gateway/modules/settings/repository.go (1)
internal/sms-gateway/modules/settings/models.go (1)
  • DeviceSettings (10-17)
internal/sms-gateway/modules/webhooks/repository.go (3)
internal/sms-gateway/modules/webhooks/models.go (1)
  • Webhook (11-25)
internal/sms-gateway/modules/devices/repository.go (2)
  • NewRepository (25-29)
  • Repository (19-23)
internal/sms-gateway/modules/messages/repository.go (2)
  • NewRepository (26-30)
  • Repository (20-24)
internal/sms-gateway/handlers/messages/params.go (1)
internal/sms-gateway/modules/messages/repository_filter.go (3)
  • SelectFilter (16-23)
  • SelectOptions (51-62)
  • Order (7-7)
internal/sms-gateway/modules/push/upstream/client.go (2)
internal/sms-gateway/modules/push/fcm/client.go (2)
  • New (21-27)
  • Client (14-19)
internal/sms-gateway/modules/push/client/types.go (2)
  • Client (9-13)
  • Message (15-18)
🪛 GitHub Check: Lint
internal/sms-gateway/modules/webhooks/repository.go

[failure] 15-15:
webhooks.Repository is missing field empty (exhaustruct)

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

@capcom6 capcom6 force-pushed the actions/migrate-to-goreleaser branch from 8bbe45b to db4cd00 Compare November 25, 2025 02:04
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 (2)
internal/sms-gateway/modules/sse/service.go (1)

54-105: Remove the default case from Send—event delivery should not drop messages; restore blocking backpressure.

The current Send method (lines 74–92) includes a default case that drops events when conn.channel is full, contradicting prior learnings that blocking on full channels was intentional and preferred. This is a regression in delivery guarantees—consider removing the default branch so backpressure remains blocking rather than lossy.

Fix ActiveConnections metric decrement in graceful shutdown.

Service.Close() (lines 107–118) closes closeSignal and deletes map entries directly without calling removeConnection. Since registerConnection increments ActiveConnections and only removeConnection decrements it, the metric will not be updated during graceful shutdown. Either loop through connections and call removeConnection for each, or decrement the metric alongside the map cleanup in Close().

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

56-64: Avoid surfacing wrapped internal errors to clients in 3rd‑party settings handlers

In get, put, and patch you return wrapped errors directly:

  • Line 60: fmt.Errorf("failed to get settings: %w", err)
  • Line 94: fmt.Errorf("failed to update settings: %w", err)
  • Line 127: fmt.Errorf("failed to update settings: %w", err)

Since this router does not use a custom errorHandler, these errors will be rendered by Fiber’s global error handler, potentially exposing internal DB/config details (and any context the service attaches) to API consumers. In the mobile settings handler you already switched to logging and returning a generic 500, which is safer.

Consider logging the detailed error and returning a generic 500 here as well, for example:

 func (h *ThirdPartyController) get(user models.User, c *fiber.Ctx) error {
 	settings, err := h.settingsSvc.GetSettings(user.ID, true)
 	if err != nil {
-		return fmt.Errorf("failed to get settings: %w", err)
+		h.Logger.Error("failed to get settings",
+			zap.Error(err),
+			zap.String("user_id", user.ID),
+		)
+		return fiber.NewError(fiber.StatusInternalServerError, "Failed to get settings")
 	}
@@
 	updated, err := h.settingsSvc.ReplaceSettings(user.ID, settings)
 
 	if err != nil {
-		return fmt.Errorf("failed to update settings: %w", err)
+		h.Logger.Error("failed to replace settings",
+			zap.Error(err),
+			zap.String("user_id", user.ID),
+		)
+		return fiber.NewError(fiber.StatusInternalServerError, "Failed to update settings")
 	}
@@
 	updated, err := h.settingsSvc.UpdateSettings(user.ID, settings)
 	if err != nil {
-		return fmt.Errorf("failed to update settings: %w", err)
+		h.Logger.Error("failed to update settings",
+			zap.Error(err),
+			zap.String("user_id", user.ID),
+		)
+		return fiber.NewError(fiber.StatusInternalServerError, "Failed to update settings")
 	}

This aligns with the mobile handler and avoids leaking internal errors to clients.

Also applies to: 79-98, 113-131

♻️ Duplicate comments (10)
internal/sms-gateway/handlers/upstream.go (1)

66-69: Validation error status code fix confirmed.

The change from StatusBadGateway to StatusBadRequest correctly returns HTTP 400 for client validation errors. This addresses the previously flagged issue.

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

62-62: Remove the unnecessary type conversion.

This was flagged in a previous review. Since UpstreamPushRequest is a type alias for []smsgateway.PushNotification, and payload is already that type, the explicit conversion is redundant.

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

9-13: Naming collision with webhooks.ValidationError remains unresolved.

This is the same issue previously flagged: messages.ValidationError (string type) collides with webhooks.ValidationError (struct type), which will require package aliases if both packages are imported together.

internal/version/version.go (1)

7-7: Fix the grammatical error in the comment.

The comment still contains a grammatical error: "when build" should be "when building" or "during build". This was identified in a previous review but not fully addressed.

Apply this diff to fix the grammar:

-// This information will be collected when build, by `-ldflags "-X main.appVersion=0.1"`.
+// This information will be collected during build, by `-ldflags "-X main.appVersion=0.1"`.
internal/sms-gateway/modules/settings/module.go (1)

9-23: fx.Private ordering still doesn’t make newRepository private

In:

fx.Provide(
    newRepository,
    fx.Private,
),

fx.Private only applies to constructors that come after it in the same fx.Provide call, so newRepository remains visible to the wider graph and fx.Private is effectively a no‑op here. If you intend newRepository to be module‑private, flip the order:

fx.Provide(
    fx.Private,
    newRepository,
)

Otherwise, consider removing fx.Private altogether to avoid confusion.

In uber-go/fx, does `fx.Private` only apply to constructors that follow it in the same `fx.Provide`/`fx.Supply` call (i.e., `fx.Provide(fx.Private, c1)`), and not to constructors listed before it (e.g., `fx.Provide(c1, fx.Private)`?
internal/config/module.go (1)

22-37: Revisit HTTP API Path defaulting condition (Host vs Path emptiness).

In the handlers.Config provider, cfg.HTTP.API.Path is defaulted to "/api" when cfg.HTTP.API.Host == "". This will overwrite any explicit Path a user configured while intentionally leaving Host empty (e.g., to derive host from the request), which was the concern in the earlier review.

To avoid clobbering user‑supplied paths, consider changing the condition to default only when Path itself is empty:

-        // Default and normalize API path/host
-        if cfg.HTTP.API.Host == "" {
-            cfg.HTTP.API.Path = "/api"
-        }
+        // Default and normalize API path/host
+        if cfg.HTTP.API.Path == "" {
+            cfg.HTTP.API.Path = "/api"
+        }

The subsequent normalization and host‑scheme stripping can stay as‑is.

Also applies to: 88-108

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

189-200: Standardize capitalization of user-facing 5xx error messages

The 500 responses here still start with lowercase “failed”:

  • Line 199: "failed to retrieve message history"
  • Line 231: "failed to get message state"
  • Line 265: "failed to get device"
  • Line 269: "failed to export inbox"
  • Line 312: "failed to handle request"

Earlier comments on this file suggested capitalizing these for consistency (“Failed …”). Not required functionally, but worth aligning if you’re standardizing error text.

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

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

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

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

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

Also applies to: 230-231, 263-269, 311-312


275-313: Centralized errorHandler nicely maps domain errors to HTTP codes

The middleware cleanly:

  • lets existing *fiber.Error pass through
  • maps messages.ValidationError and other messages/devices sentinels to 400/404/409
  • falls back to a generic 500 with logging.

The switch on value-typed messages.ValidationError fixes the earlier pointer-type matching issue. This should give consistent HTTP responses across handlers.

internal/sms-gateway/handlers/messages/params.go (1)

24-27: Fix date-range validation to compare parsed timestamps, not strings

p.StartDate > p.EndDate is a string comparison, so valid ranges with different offsets can be incorrectly rejected (e.g., "2025-11-10T12:00:00+02:00" vs "2025-11-10T11:00:00+00:00").

Since the fields are already RFC3339-validated, parse both to time.Time 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
+		}
+	}

This was flagged previously; the current version still uses string comparison.

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

176-218: Address TTL overflow risk and normalize Enqueue's error contract

Both concerns identified are valid and should be addressed:

  1. TTL → time.Duration overflow (security/robustness).
    Confirmed: MessageIn.TTL is *uint64 (unbounded). Casting to time.Duration (int64) without validation will silently overflow for large values. The //nolint:gosec comment lacks justification. Implement the suggested bounds check in prepareMessage:

    	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),
    
  •   )
    
  •   const maxTTLSeconds = 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),
    
  •   )
    
    }
    
    
  1. Enqueue returns non-nil state when insert fails.
    Confirmed: Line 200 returns populated state alongside error, which is unconventional. While the handler (3rdparty.go:135) correctly ignores state on error, the pattern violates Go conventions. Change to return nil on Insert failure:

    	if insErr := s.messages.Insert(msg); insErr != nil {
  •   return state, insErr
    
  •   return nil, insErr
    
    }
    
    
🧹 Nitpick comments (13)
internal/sms-gateway/modules/db/health.go (1)

17-23: Redundant explicit zero-value initialization.

atomic.Int64 is zero-initialized by default in Go, so failedPings: atomic.Int64{} is unnecessary. That said, keeping it explicit for clarity is a valid stylistic choice.

 func newHealth(db *sql.DB) *health {
 	return &health{
-		db: db,
-
-		failedPings: atomic.Int64{},
+		db: db,
 	}
 }
internal/sms-gateway/modules/push/upstream/client.go (2)

29-35: Consider configuring HTTP client timeout.

The http.Client is created without a timeout, which could cause requests to hang indefinitely. Consider setting a reasonable timeout:

-	c.client = &http.Client{}
+	c.client = &http.Client{
+		Timeout: 30 * time.Second,
+	}

105-112: Consider closing idle connections before releasing the client.

Setting c.client = nil doesn't close underlying TCP connections. For proper resource cleanup, call CloseIdleConnections():

 func (c *Client) Close(_ context.Context) error {
 	c.mux.Lock()
 	defer c.mux.Unlock()
 
+	if c.client != nil {
+		c.client.CloseIdleConnections()
+	}
 	c.client = nil
 
 	return nil
 }
internal/worker/locker/mysql.go (1)

29-29: Optional: Redundant explicit zero-value initialization.

The explicit sync.Mutex{} initialization is redundant since Go zero-initializes struct fields. However, this is purely stylistic and doesn't affect functionality.

Consider simplifying to:

-		mu:    sync.Mutex{},

The field will be automatically zero-initialized, which is the correct state for an unused mutex.

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

275-276: Add comment explaining ignored error or handle it.

The past review comment (marked as addressed) suggested either handling the error from nanoid.Standard() or adding a comment explaining why it's safe to ignore. The error is still being ignored without explanation.

While the error cannot occur with size 21 and the default alphabet, a brief comment would clarify this for future maintainers:

 	const idGenSize = 21
+	// nanoid.Standard with size 21 never returns an error
 	idGen, _ := nanoid.Standard(idGenSize)

Alternatively, handle the error defensively:

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

20-22: Remove the no-op OnStart function.

The OnStart hook that returns nil serves no purpose. When OnStart is not needed, you can omit the field entirely or set it to nil directly.

Apply this diff:

 			lc.Append(fx.Hook{
-				OnStart: func(_ context.Context) error {
-					return nil
-				},
 				OnStop: func(_ context.Context) error {
internal/sms-gateway/modules/messages/workers.go (1)

32-32: Remove redundant explicit initialization.

The explicit sync.Mutex{} initialization is redundant since the zero value of sync.Mutex is already a valid, unlocked mutex.

Apply this diff:

 		queue: map[uint64]struct{}{},
-		mux:   sync.Mutex{},
 	}
internal/sms-gateway/modules/sse/module.go (1)

25-25: Consider omitting the explicit nil OnStart.

When no startup logic is needed, the OnStart field can be omitted entirely rather than explicitly setting it to nil.

Apply this diff:

 		fx.Invoke(func(lc fx.Lifecycle, svc *Service) {
 			lc.Append(fx.Hook{
-				OnStart: nil,
 				OnStop: func(ctx context.Context) error {
internal/sms-gateway/modules/settings/models.go (1)

10-25: DeviceSettings embedding and constructor look good; only optional consideration on map aliasing

Embedding models.TimedModel and the NewDeviceSettings partial constructor are consistent with the rest of the models and avoid exhaustruct noise. The direct reuse of the settings map is efficient; if you ever need to protect stored settings from later caller mutation, consider making a shallow copy inside NewDeviceSettings.

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

1-7: ErrNoConnection sentinel is fine; consider adding a brief doc comment

The exported ErrNoConnection sentinel is appropriate for signaling missing SSE connections and works well with errors.Is. For discoverability, you might add a short comment like // ErrNoConnection indicates that no SSE connection exists for the given key. above it.

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

29-61: UpdateSettings transactional merge looks solid; minor nit on return value in error paths

The transactional flow—locking the existing row by user_id, normalizing source.Settings, merging with appendMap, and upserting via OnConflict{UpdateAll: true}—is a good pattern for avoiding lost updates on concurrent writes.

One minor clarity nit: on error, you return updatedSettings along with the wrapped error, but for most failure paths updatedSettings will still be nil (and in the rare case of a commit‑time error, it will contain the desired state but not actually be persisted). If callers never inspect the value when err != nil, you’re fine; otherwise, consider returning nil in the error case to make that contract explicit.


63-76: ReplaceSettings implementation is straightforward; consider nil value on error for clarity

Wrapping tx.Save(settings) in a transaction and standardizing the error message as "failed to replace settings: %w" is fine. As with UpdateSettings, if you want to tighten the API contract, you could choose to return nil alongside the error instead of settings when err != nil, to avoid any implication that the returned value reflects persisted state.

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

49-57: Good: PII kept in logs, generic 500 to client

Switching from a formatted error (with IDs) to structured logging plus fiber.NewError(500, "failed to get settings") avoids leaking device/user IDs in responses while preserving context in logs. If you care about consistency with other handlers, consider capitalizing the message (“Failed to get settings”), but that’s cosmetic.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 8bbe45b and db4cd00.

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

Applied to files:

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

Applied to files:

  • internal/sms-gateway/modules/sse/errors.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
  • internal/sms-gateway/modules/messages/service.go
🧬 Code graph analysis (35)
internal/sms-gateway/pubsub/pubsub.go (4)
internal/config/config.go (2)
  • Config (10-20)
  • PubSub (84-87)
pkg/pubsub/options.go (1)
  • WithBufferSize (15-19)
pkg/pubsub/memory.go (1)
  • NewMemory (24-38)
pkg/pubsub/redis.go (2)
  • NewRedis (42-78)
  • RedisConfig (15-27)
internal/sms-gateway/modules/settings/models.go (4)
internal/sms-gateway/models/models.go (2)
  • TimedModel (7-10)
  • User (18-24)
internal/sms-gateway/models/migration.go (1)
  • Migrate (13-18)
internal/sms-gateway/modules/messages/models.go (1)
  • Migrate (164-169)
internal/sms-gateway/modules/webhooks/models.go (1)
  • Migrate (38-43)
internal/sms-gateway/handlers/events/mobile.go (5)
internal/sms-gateway/handlers/messages/mobile.go (1)
  • MobileController (29-33)
internal/sms-gateway/handlers/settings/mobile.go (1)
  • MobileController (14-19)
internal/sms-gateway/handlers/webhooks/mobile.go (1)
  • MobileController (15-19)
internal/sms-gateway/models/models.go (1)
  • Device (34-45)
internal/sms-gateway/handlers/base/handler.go (1)
  • Handler (15-18)
internal/sms-gateway/modules/metrics/module.go (7)
internal/sms-gateway/app.go (1)
  • Module (33-58)
internal/sms-gateway/modules/db/module.go (1)
  • Module (16-26)
internal/sms-gateway/modules/messages/module.go (1)
  • Module (10-25)
internal/sms-gateway/modules/push/module.go (1)
  • Module (11-36)
internal/sms-gateway/modules/sse/module.go (1)
  • Module (10-32)
internal/sms-gateway/modules/webhooks/module.go (1)
  • Module (9-20)
internal/sms-gateway/modules/sse/config.go (1)
  • Option (5-5)
internal/sms-gateway/modules/devices/repository.go (2)
internal/sms-gateway/handlers/middlewares/deviceauth/deviceauth.go (1)
  • New (20-44)
internal/sms-gateway/models/models.go (1)
  • Device (34-45)
internal/sms-gateway/modules/messages/converters.go (1)
internal/sms-gateway/modules/messages/domain.go (1)
  • MessageOut (25-29)
internal/sms-gateway/modules/push/fcm/utils.go (4)
internal/sms-gateway/modules/events/types.go (1)
  • Event (10-13)
internal/sms-gateway/modules/push/client/types.go (1)
  • Event (20-23)
internal/sms-gateway/modules/push/types.go (1)
  • Event (17-17)
internal/sms-gateway/modules/sse/types.go (1)
  • Event (7-10)
internal/sms-gateway/handlers/messages/mobile.go (5)
internal/sms-gateway/handlers/events/mobile.go (2)
  • NewMobileController (19-27)
  • MobileController (13-17)
internal/sms-gateway/handlers/settings/mobile.go (2)
  • NewMobileController (21-35)
  • MobileController (14-19)
internal/sms-gateway/handlers/webhooks/mobile.go (2)
  • NewMobileController (21-33)
  • MobileController (15-19)
internal/sms-gateway/handlers/base/handler.go (1)
  • Handler (15-18)
internal/sms-gateway/models/models.go (1)
  • Device (34-45)
internal/sms-gateway/handlers/messages/3rdparty.go (4)
internal/sms-gateway/handlers/base/handler.go (1)
  • Handler (15-18)
internal/sms-gateway/modules/messages/service.go (1)
  • EnqueueOptions (22-24)
internal/sms-gateway/modules/messages/errors.go (2)
  • ValidationError (9-9)
  • ErrNoContent (6-6)
internal/sms-gateway/modules/devices/repository.go (3)
  • ErrNotFound (14-14)
  • ErrInvalidFilter (15-15)
  • ErrMoreThanOne (16-16)
internal/sms-gateway/modules/webhooks/service.go (2)
internal/sms-gateway/modules/push/types.go (1)
  • Event (17-17)
internal/sms-gateway/modules/webhooks/errors.go (1)
  • ErrInvalidEvent (9-9)
internal/sms-gateway/handlers/middlewares/userauth/userauth.go (1)
internal/sms-gateway/models/models.go (1)
  • User (18-24)
internal/sms-gateway/handlers/settings/3rdparty.go (4)
internal/sms-gateway/handlers/logs/3rdparty.go (2)
  • NewThirdPartyController (24-31)
  • ThirdPartyController (20-22)
internal/sms-gateway/handlers/base/handler.go (1)
  • Handler (15-18)
internal/sms-gateway/models/models.go (1)
  • User (18-24)
internal/sms-gateway/modules/settings/models.go (1)
  • DeviceSettings (10-17)
internal/sms-gateway/modules/webhooks/models.go (4)
internal/sms-gateway/models/models.go (3)
  • SoftDeletableModel (12-16)
  • User (18-24)
  • Device (34-45)
internal/sms-gateway/models/migration.go (1)
  • Migrate (13-18)
internal/sms-gateway/modules/messages/models.go (1)
  • Migrate (164-169)
internal/sms-gateway/modules/settings/models.go (1)
  • Migrate (27-32)
internal/sms-gateway/modules/db/health.go (1)
pkg/health/types.go (2)
  • Checks (67-67)
  • Provider (69-75)
internal/sms-gateway/modules/messages/cache.go (2)
internal/sms-gateway/modules/messages/domain.go (1)
  • MessageStateOut (38-44)
pkg/cache/options.go (2)
  • WithTTL (20-31)
  • AndSetTTL (65-69)
internal/sms-gateway/modules/sse/service.go (2)
internal/sms-gateway/modules/sse/errors.go (1)
  • ErrNoConnection (6-6)
internal/sms-gateway/modules/sse/metrics.go (2)
  • ErrorTypeMarshalError (22-22)
  • ErrorTypeWriteFailure (21-21)
internal/config/module.go (3)
internal/config/config.go (13)
  • Config (10-20)
  • Default (89-127)
  • HTTP (27-33)
  • Database (44-55)
  • Gateway (22-25)
  • API (35-38)
  • GatewayModePublic (6-6)
  • OpenAPI (40-42)
  • Messages (75-78)
  • Tasks (63-65)
  • SSE (71-73)
  • Cache (80-82)
  • PubSub (84-87)
internal/sms-gateway/modules/auth/service.go (1)
  • Config (20-23)
internal/sms-gateway/handlers/config.go (1)
  • Config (3-11)
internal/sms-gateway/app.go (9)
internal/config/module.go (1)
  • Module (23-140)
internal/sms-gateway/modules/db/module.go (1)
  • Module (16-26)
internal/sms-gateway/modules/metrics/module.go (1)
  • Module (9-19)
internal/sms-gateway/modules/push/module.go (1)
  • Module (11-36)
internal/sms-gateway/modules/settings/module.go (1)
  • Module (9-23)
internal/sms-gateway/modules/webhooks/module.go (1)
  • Module (9-20)
internal/sms-gateway/modules/devices/module.go (1)
  • Module (8-18)
internal/sms-gateway/online/module.go (1)
  • Module (11-36)
internal/sms-gateway/cache/module.go (1)
  • Module (8-16)
internal/sms-gateway/modules/push/upstream/client.go (1)
internal/sms-gateway/modules/push/client/types.go (2)
  • Client (9-13)
  • Message (15-18)
internal/sms-gateway/modules/events/module.go (8)
internal/sms-gateway/modules/messages/module.go (1)
  • Module (10-25)
internal/sms-gateway/modules/metrics/module.go (1)
  • Module (9-19)
internal/sms-gateway/modules/settings/module.go (1)
  • Module (9-23)
internal/sms-gateway/modules/sse/module.go (1)
  • Module (10-32)
internal/sms-gateway/modules/webhooks/module.go (1)
  • Module (9-20)
internal/sms-gateway/pubsub/module.go (1)
  • Module (11-33)
internal/sms-gateway/modules/events/service.go (2)
  • NewService (33-52)
  • Service (20-31)
internal/sms-gateway/modules/messages/service.go (2)
  • NewService (40-63)
  • Service (26-38)
internal/sms-gateway/modules/push/client/types.go (1)
internal/sms-gateway/modules/messages/models.go (1)
  • Message (36-56)
internal/worker/locker/mysql.go (1)
internal/worker/locker/locker.go (1)
  • ErrLockNotAcquired (9-9)
internal/sms-gateway/handlers/messages/params.go (1)
internal/sms-gateway/modules/messages/repository_filter.go (3)
  • SelectFilter (16-23)
  • SelectOptions (51-62)
  • Order (7-7)
internal/sms-gateway/handlers/settings/mobile.go (4)
internal/sms-gateway/handlers/base/handler.go (1)
  • Handler (15-18)
internal/sms-gateway/handlers/events/mobile.go (2)
  • NewMobileController (19-27)
  • MobileController (13-17)
internal/sms-gateway/handlers/messages/mobile.go (2)
  • NewMobileController (35-43)
  • MobileController (29-33)
internal/sms-gateway/handlers/webhooks/mobile.go (2)
  • NewMobileController (21-33)
  • MobileController (15-19)
internal/sms-gateway/modules/messages/models.go (1)
internal/sms-gateway/models/models.go (1)
  • SoftDeletableModel (12-16)
internal/sms-gateway/handlers/devices/3rdparty.go (4)
internal/sms-gateway/handlers/base/handler.go (1)
  • Handler (15-18)
internal/sms-gateway/handlers/logs/3rdparty.go (2)
  • NewThirdPartyController (24-31)
  • ThirdPartyController (20-22)
internal/sms-gateway/handlers/messages/3rdparty.go (2)
  • NewThirdPartyController (44-53)
  • ThirdPartyController (37-42)
internal/sms-gateway/handlers/webhooks/3rdparty.go (2)
  • NewThirdPartyController (32-40)
  • ThirdPartyController (26-30)
internal/sms-gateway/modules/settings/module.go (7)
internal/sms-gateway/app.go (1)
  • Module (33-58)
internal/sms-gateway/modules/db/module.go (1)
  • Module (16-26)
internal/sms-gateway/modules/messages/module.go (1)
  • Module (10-25)
internal/sms-gateway/modules/metrics/module.go (1)
  • Module (9-19)
internal/sms-gateway/modules/sse/module.go (1)
  • Module (10-32)
internal/sms-gateway/modules/webhooks/module.go (1)
  • Module (9-20)
internal/sms-gateway/modules/settings/service.go (1)
  • NewService (27-35)
internal/worker/executor/service.go (1)
internal/worker/config/types.go (1)
  • Duration (11-11)
internal/config/config.go (3)
internal/sms-gateway/modules/messages/config.go (1)
  • Config (5-8)
internal/sms-gateway/pubsub/config.go (1)
  • Config (4-7)
internal/sms-gateway/cache/config.go (1)
  • Config (4-6)
internal/sms-gateway/handlers/mobile.go (1)
internal/sms-gateway/models/models.go (2)
  • Device (34-45)
  • User (18-24)
internal/sms-gateway/modules/messages/errors.go (1)
internal/sms-gateway/modules/webhooks/errors.go (1)
  • ValidationError (12-16)
internal/sms-gateway/modules/messages/repository_filter.go (4)
internal/sms-gateway/modules/devices/repository_filter.go (2)
  • SelectFilter (9-9)
  • WithUserID (23-27)
internal/sms-gateway/modules/webhooks/repository_filter.go (4)
  • SelectFilter (5-5)
  • WithExtID (7-11)
  • WithUserID (13-17)
  • WithDeviceID (41-46)
internal/sms-gateway/modules/messages/models.go (1)
  • ProcessingState (13-13)
internal/sms-gateway/handlers/middlewares/deviceauth/deviceauth.go (1)
  • WithDevice (83-87)
internal/sms-gateway/modules/messages/repository.go (2)
internal/sms-gateway/modules/messages/repository_filter.go (2)
  • SelectFilter (16-23)
  • Order (7-7)
internal/sms-gateway/modules/messages/models.go (3)
  • Message (36-56)
  • ProcessingStatePending (17-17)
  • MessageRecipient (139-145)
internal/sms-gateway/modules/settings/repository.go (1)
internal/sms-gateway/modules/settings/models.go (1)
  • DeviceSettings (10-17)
internal/sms-gateway/modules/push/types.go (3)
internal/sms-gateway/modules/events/types.go (1)
  • Event (10-13)
internal/sms-gateway/modules/push/client/types.go (1)
  • Event (20-23)
internal/sms-gateway/modules/sse/types.go (1)
  • Event (7-10)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (4)
  • GitHub Check: Benchmark
  • GitHub Check: Test
  • GitHub Check: E2E
  • GitHub Check: Analyze (go)

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 (3)
test/e2e/webhooks_test.go (3)

118-119: Avoid variable shadowing.

The variable err of type errorResponse is shadowed by the error return from json.Unmarshal. Use distinct names (e.g., errResp for the response struct) to improve clarity.

Apply this diff to fix the shadowing:

-			var err errorResponse
-			if err := json.Unmarshal(response.Body(), &err); err != nil {
+			var errResp errorResponse
+			if err := json.Unmarshal(response.Body(), &errResp); err != nil {
 				t.Fatal(err)
 			}
 
-			if err.Message == "" {
+			if errResp.Message == "" {
 				t.Error("expected error message in response")
 			}

Also applies to: 142-143


306-307: Avoid variable shadowing (same issue as in TestWebhooks_Get).

Use distinct variable names for the error response struct to avoid shadowing the error from json.Unmarshal.

Apply the same pattern as suggested for TestWebhooks_Get:

-			var err errorResponse
-			if err := json.Unmarshal(response.Body(), &err); err != nil {
+			var errResp errorResponse
+			if err := json.Unmarshal(response.Body(), &errResp); err != nil {
 				t.Fatal(err)
 			}
 
-			if err.Message == "" {
+			if errResp.Message == "" {
 				t.Error("expected error message in response")
 			}

Also applies to: 333-334, 361-362, 390-391, 418-419, 446-447


174-476: Consider adding cleanup for created webhooks.

The test creates multiple webhooks across different cases but doesn't clean them up. Adding t.Cleanup() calls to delete created webhooks would prevent test pollution and ensure test isolation.

Example pattern to add in successful creation test cases:

validate: func(t *testing.T, response *resty.Response) {
	var result webhook
	if err := json.Unmarshal(response.Body(), &result); err != nil {
		t.Fatal(err)
	}
	
	// Add cleanup
	t.Cleanup(func() {
		authorizedClient.R().Delete("webhooks/" + result.ID)
	})
	
	// ... existing validation
}
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between db4cd00 and edccb03.

📒 Files selected for processing (1)
  • test/e2e/webhooks_test.go (1 hunks)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (4)
  • GitHub Check: Test
  • GitHub Check: Benchmark
  • GitHub Check: E2E
  • GitHub Check: Analyze (go)
🔇 Additional comments (2)
test/e2e/webhooks_test.go (2)

1-15: LGTM!

The imports and webhook data model are appropriate for the e2e testing requirements.


549-556: LGTM! Good variable naming.

These validation functions correctly avoid shadowing by using errResp for the response struct and err for the unmarshal error. This is the pattern that should be used throughout.

Also applies to: 569-576

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/webhooks/service.go (1)

72-90: Good security improvement with device ownership validation.

The signature change to accept a pointer and the addition of device ownership validation strengthen the API's security posture. The validation correctly checks if the device exists and belongs to the user before proceeding.

However, the error message at line 85 could be more descriptive:

 		ok, err := s.devicesSvc.Exists(userID, devices.WithID(*webhook.DeviceID))
 		if err != nil {
-			return fmt.Errorf("failed to select devices: %w", err)
+			return fmt.Errorf("failed to verify device ownership: %w", err)
 		}
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between edccb03 and bb457cd.

📒 Files selected for processing (2)
  • internal/sms-gateway/handlers/webhooks/3rdparty.go (4 hunks)
  • internal/sms-gateway/modules/webhooks/service.go (5 hunks)
🧰 Additional context used
🧬 Code graph analysis (2)
internal/sms-gateway/handlers/webhooks/3rdparty.go (6)
internal/sms-gateway/handlers/logs/3rdparty.go (2)
  • NewThirdPartyController (24-31)
  • ThirdPartyController (20-22)
internal/sms-gateway/handlers/base/handler.go (1)
  • Handler (15-18)
internal/sms-gateway/models/models.go (1)
  • User (18-24)
internal/sms-gateway/modules/webhooks/models.go (1)
  • Webhook (11-25)
internal/sms-gateway/modules/webhooks/errors.go (1)
  • IsValidationError (34-36)
internal/sms-gateway/modules/webhooks/repository_filter.go (1)
  • WithExtID (7-11)
internal/sms-gateway/modules/webhooks/service.go (2)
internal/sms-gateway/modules/webhooks/models.go (1)
  • Webhook (11-25)
internal/sms-gateway/modules/webhooks/errors.go (1)
  • ErrInvalidEvent (9-9)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (4)
  • GitHub Check: Benchmark
  • GitHub Check: Test
  • GitHub Check: E2E
  • GitHub Check: Analyze (go)
🔇 Additional comments (7)
internal/sms-gateway/modules/webhooks/service.go (3)

56-56: LGTM: Error message standardization.

The updated error message follows a consistent "failed to..." pattern, improving error reporting uniformity across the codebase.


92-102: LGTM: Clean factory pattern and consistent error handling.

The use of the newWebhook factory function properly encapsulates model construction, and the repository interaction correctly passes the model value. Error handling follows the standardized pattern.


114-114: LGTM: Consistent error message updates.

Error messages in Delete and notifyDevices paths now follow the standardized "failed to..." pattern for better consistency.

Also applies to: 126-126

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

32-40: LGTM: Constructor placement follows convention.

Moving the constructor to the top of the file improves code organization and aligns with Go conventions. The dependency injection pattern using fx.In is properly implemented.


52-59: LGTM: Consistent error messaging.

The standardized error message improves consistency across the codebase.


76-92: LGTM: Consistent pointer-based flow and improved error handling.

The change to pointer-based DTO handling aligns with the service signature change and maintains proper flow:

  1. Creates pointer DTO
  2. Parses and validates in place
  3. Passes pointer to service

The updated error handling using fiber.NewError provides clearer HTTP error responses.


105-114: LGTM: Error message standardization.

The updated error message maintains consistency with the broader error handling pattern across the codebase.

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)
test/e2e/webhooks_test.go (1)

208-213: Avoid t.Fatal in cleanup functions.

Using t.Fatal in cleanup functions registered with t.Cleanup can prevent other cleanup functions from running if this one fails. Consider using t.Error instead, or simply ignore cleanup errors since they're non-critical.

Apply this pattern to all cleanup functions (also at lines 251-256, 296-301):

 t.Cleanup(func() {
-    _, err := authorizedClient.R().Delete("webhooks/" + result.ID)
-    if err != nil {
-        t.Fatal(err)
-    }
+    authorizedClient.R().Delete("webhooks/" + result.ID)
+    // Ignore errors in cleanup - resource may already be deleted
 })
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between bb457cd and cf52274.

📒 Files selected for processing (2)
  • internal/sms-gateway/modules/webhooks/service.go (5 hunks)
  • test/e2e/webhooks_test.go (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
internal/sms-gateway/modules/webhooks/service.go (3)
internal/sms-gateway/modules/webhooks/models.go (1)
  • Webhook (11-25)
internal/sms-gateway/modules/webhooks/errors.go (1)
  • ErrInvalidEvent (9-9)
internal/sms-gateway/modules/devices/repository.go (1)
  • ErrNotFound (14-14)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (4)
  • GitHub Check: Benchmark
  • GitHub Check: Test
  • GitHub Check: E2E
  • GitHub Check: Analyze (go)
🔇 Additional comments (10)
internal/sms-gateway/modules/webhooks/service.go (5)

72-74: LGTM - Pointer parameter and improved validation.

The change to accept a pointer parameter is correct since the method mutates webhook.ID at line 78. The event validation now uses ErrInvalidEvent and passes webhook.Event directly, which is cleaner than the previous approach.


82-90: Excellent security improvement - device ownership validation.

The added device ownership verification prevents users from creating webhooks for devices they don't own. The implementation correctly checks for nil DeviceID, verifies ownership, and returns appropriate validation errors.


92-98: LGTM - Helper function improves maintainability.

Replacing the manual struct literal with the newWebhook helper function is a good refactoring that improves code maintainability and reduces duplication.


56-126: LGTM - Error message consistency improvements.

All error messages have been updated from "can't X" to "failed to X", improving consistency and professionalism across the codebase.


100-101: ****

The code is correct. The newWebhook() function returns *Webhook (a pointer), not a value. Since Repository.Replace() expects webhook *Webhook, the call at line 100 correctly passes a pointer, not a value. There is no type mismatch.

Likely an incorrect or invalid review comment.

test/e2e/webhooks_test.go (5)

17-172: LGTM - Comprehensive GET endpoint tests.

The test function covers all important scenarios: empty list, populated list, and authentication errors. The table-driven test structure is clean and follows Go best practices.


174-497: LGTM - Comprehensive POST endpoint tests with excellent coverage.

The test function covers all important scenarios: valid creation with various configurations, validation errors, and authentication errors. The table-driven structure is clean and maintainable.


510-539: LGTM - Previous issues have been resolved.

The webhook deletion test has been properly fixed:

  • setup() now returns the server-generated webhook ID
  • The POST request no longer includes an ID in the body
  • The request() function correctly receives and uses the webhook ID
  • All scope issues have been resolved

The implementation now correctly tests deletion of an actual webhook resource.


540-597: LGTM - DELETE error cases are well covered.

The tests correctly cover non-existent resource deletion (idempotent behavior) and authentication errors. The hardcoded "non-existent-id" at line 548 is appropriate for testing the non-existent resource case.


600-625: LGTM - Cleanup logic is defensive and correct.

The cleanup logic properly checks if a webhook was created before attempting to delete it. Ignoring errors during cleanup is acceptable since the API supports idempotent DELETE operations (returns 204 even for non-existent resources).

@capcom6 capcom6 force-pushed the actions/migrate-to-goreleaser branch from 0acb43d to 71fb64f Compare November 25, 2025 08:44
@capcom6 capcom6 added ready deployed The PR is deployed on staging labels Nov 25, 2025
@capcom6 capcom6 merged commit df4ea5b into master Nov 27, 2025
9 checks passed
@capcom6 capcom6 deleted the actions/migrate-to-goreleaser branch November 27, 2025 23:44
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 ready

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants