Skip to content

Use model package types instead of local duplicates#160

Open
lieut-data wants to merge 4 commits into
masterfrom
use-model-types
Open

Use model package types instead of local duplicates#160
lieut-data wants to merge 4 commits into
masterfrom
use-model-types

Conversation

@lieut-data

@lieut-data lieut-data commented Jun 1, 2026

Copy link
Copy Markdown
Member

Summary

A followup to #158 that consolidates under a common model.* structs and constants. Note that while some field names have changed, the wire names are identical between both.

Ticket Link

Relates-to: https://mattermost.atlassian.net/browse/MM-68727

}

func (me *AndroidNotificationServer) SendNotification(msg *PushNotification) PushResponse {
func (me *AndroidNotificationServer) SendNotification(_ int, msg *model.PushNotification) PushResponse {

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

appVersion not on model.PushNotification -- passed explicitly instead.


data := payload.NewPayload()
if msg.Badge == 0 && msg.Type == PushTypeClear && msg.AppVersion > 1 {
if msg.Badge == 0 && msg.Type == model.PushTypeClear && appVersion > 1 {

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

On the appVersion parameter, @enahum I noticed this was introduced in April 2022. Is there an opportunity to drop support for old appVersion, or do we want to maintain this indefinitely? I can follow up with a metrics change in a separate PR to give us visibility here if the answer isn't obvious.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I think we can drop it, at this point after 4 years no one should be using v1 off the app. I'd say definitely drop it

OverrideIconURL string `json:"override_icon_url"`
FromWebhook string `json:"from_webhook"`
Version string `json:"version"`
AppVersion int `json:"app_version,omitempty"`

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

This was the single field "missing", but also never part of the wire payload -- so just handling it discretely.

Comment thread server/server.go
Comment on lines +230 to +237
appVersion := 1
if index := strings.Index(msg.Platform, "-v"); index > -1 {
platform := msg.Platform
msg.Platform = platform[:index]
appVersionString := platform[index+2:]
version, e := strconv.Atoi(appVersionString)
if e == nil {
msg.AppVersion = version
appVersion = version

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Same logic, but not setting it on the struct anymore.

Base automatically changed from ios-ring to master June 5, 2026 22:01
@lieut-data lieut-data marked this pull request as ready for review June 5, 2026 22:23
@coderabbitai

coderabbitai Bot commented Jun 5, 2026

Copy link
Copy Markdown

Too many files changed? Review this PR in Change Stack to see how the pieces fit before you dive in.

Review Change Stack

📝 Walkthrough

Walkthrough

This PR migrates push notification handling from locally defined types to the model.PushNotification type from Mattermost's server public model package. The interface contracts are updated to accept appVersion and use model types, while Android FCM and Apple APNS implementations are refactored to use renamed fields and model constants for payload construction, metrics, and logging.

Changes

Push Notification Model Type Migration

Layer / File(s) Summary
Type contracts and server interface definition
go.mod, server/server.go, server/push_notification.go, server/metrics.go
The NotificationServer.SendNotification interface is updated to accept appVersion int and *model.PushNotification. Request handler parses app version from platform string suffix (-v), switches message and ACK payloads to model types (model.PushNotification, model.PushNotificationAck), and updates field access from ServerID/DeviceID to ServerId/DeviceId and ID/Platform/Type to Id/ClientPlatform/NotificationType. Indirect dependencies are expanded to support the model package.
Android FCM implementation with model types
server/android_notification_server.go
SendNotification accepts model.PushNotification with updated field names (AckId, SubType, RootId, SenderId, PostId). FCM payload construction rebuilds data map using model fields, branches on IsIdLoaded to populate different keys (message/session vs post-related), and updates metrics to use model constants. Logging enriches failed-send messages with redacted device ID (did) and server ID (sid).
Apple APNS implementation with model types and VoIP refactoring
server/apple_notification_server.go
SendNotification and dispatchAndHandleResponse accept model.PushNotification and model.PushTransport. Payload construction uses model push-type constants, handles PushTypeClear/PushTypeTest/PushTypeUpdateBadge branches, and populates common routing fields (server ID, ack ID, thread ID, team ID, sender ID/name, post ID, root ID). VoIP helpers (sendVoIPNotification, buildVoIPNotification) refactored to accept model notification type, build VoIP-specific payloads with id_loaded and ack_id, and dispatch using model.PushTransportVoIP. Metrics use model platform constants for routing.
Metrics helpers with typed transport
server/metrics.go
Counter and histogram increment helpers (incrementNotificationTotal, incrementSuccess, incrementSuccessWithAck, incrementDelivered, incrementFailure, incrementRemoval) updated to accept model.PushTransport and convert to string for label values. Notification response observer switch uses model.PushNotifyApple for platform-specific routing.
Test updates for model types and constants
server/apple_notification_test.go, server/metrics_test.go, server/server_test.go
All tests construct model.PushNotification payloads with updated field names and pass appVersion to SendNotification. Transport test table uses model.PushTransport values and metric assertions convert to string. Constant references use model scopes (e.g., model.PushTypeMessage, model.PushNotifyApple, model.PushNotifyAndroid). VoIP tests verify payload field names and omission behavior (missing signature defaults to "NO_SIGNATURE", missing ack ID is omitted).
Token redaction helper for safe logging
server/push_notification.go
Utility function redactToken truncates device tokens longer than 8 characters to first 8 characters plus ellipsis for secure logging in error messages.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

  • mattermost/mattermost-push-proxy#158: Both PRs modify the iOS/APNs VoIP path in server/apple_notification_server.go and related transport/metrics plumbing, with this PR refactoring VoIP dispatch and payload logic to use server/public/model typed constants and signatures.
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 16.67% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately and concisely describes the main objective of the changeset: replacing local duplicate types and constants with shared model package types.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Description check ✅ Passed The pull request description is directly related to the changeset, explaining the consolidation of local types with model package equivalents.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch use-model-types

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

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Caution

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

⚠️ Outside diff range comments (2)
server/android_notification_server.go (1)

251-256: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Cancel retry context per iteration instead of deferring in the loop.

defer cancelRetryContext() (server/android_notification_server.go, lines 251-252) is executed only when the enclosing function returns, so timeout resources can stay alive across retries. Call cancelRetryContext() inside each retry iteration instead.

Suggested fix
 	retryContext, cancelRetryContext := context.WithTimeout(generalContext, me.retryTimeout)
-	defer cancelRetryContext()
 	_, err = me.client.Send(retryContext, fcmMsg)
+	cancelRetryContext()
 	if me.metrics != nil {
 		me.metrics.observerNotificationResponse(model.PushNotifyAndroid, time.Since(start).Seconds())
 	}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@server/android_notification_server.go` around lines 251 - 256, The defer
cancelRetryContext() inside the retry loop keeps each retry's timeout alive
until the enclosing function returns; replace the deferred call with an explicit
cancelRetryContext() invocation at the end of each iteration (after
me.client.Send and metrics.observerNotificationResponse) so the context created
by context.WithTimeout(generalContext, me.retryTimeout) is cancelled per
iteration; ensure retryContext and cancelRetryContext are still created each
loop and that cancelRetryContext() is always called before continuing or
breaking the loop.
server/apple_notification_server.go (1)

381-386: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Avoid deferring cancelRetryContext() inside the retry loop

cancelRetryContext() is deferred on every retry iteration, so cancellations (and the underlying timer/resources for context.WithTimeout) won’t run until the enclosing function returns. Cancel the attempt immediately after PushWithContext returns.

Suggested fix
 	retryContext, cancelRetryContext := context.WithTimeout(generalContext, me.retryTimeout)
-	defer cancelRetryContext()
 	res, err = me.AppleClient.PushWithContext(retryContext, notification)
+	cancelRetryContext()
 	if me.metrics != nil {
 		me.metrics.observerNotificationResponse(model.PushNotifyApple, time.Since(start).Seconds())
 	}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@server/apple_notification_server.go` around lines 381 - 386, The retry loop
is deferring cancelRetryContext() each iteration which delays cancelling the
per-attempt timeout until the enclosing function returns; instead, remove the
defer and call cancelRetryContext() immediately after the call to
me.AppleClient.PushWithContext (and after recording metrics via
me.metrics.observerNotificationResponse) so the attempt's context resources are
released promptly; locate the retry loop that creates
retryContext/cancelRetryContext and replace the defer with a direct
cancelRetryContext() call after res, err = me.AppleClient.PushWithContext(...)
and metrics handling.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Outside diff comments:
In `@server/android_notification_server.go`:
- Around line 251-256: The defer cancelRetryContext() inside the retry loop
keeps each retry's timeout alive until the enclosing function returns; replace
the deferred call with an explicit cancelRetryContext() invocation at the end of
each iteration (after me.client.Send and metrics.observerNotificationResponse)
so the context created by context.WithTimeout(generalContext, me.retryTimeout)
is cancelled per iteration; ensure retryContext and cancelRetryContext are still
created each loop and that cancelRetryContext() is always called before
continuing or breaking the loop.

In `@server/apple_notification_server.go`:
- Around line 381-386: The retry loop is deferring cancelRetryContext() each
iteration which delays cancelling the per-attempt timeout until the enclosing
function returns; instead, remove the defer and call cancelRetryContext()
immediately after the call to me.AppleClient.PushWithContext (and after
recording metrics via me.metrics.observerNotificationResponse) so the attempt's
context resources are released promptly; locate the retry loop that creates
retryContext/cancelRetryContext and replace the defer with a direct
cancelRetryContext() call after res, err = me.AppleClient.PushWithContext(...)
and metrics handling.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: c3c0fb74-f6dc-4c47-bd36-afc8a13ded88

📥 Commits

Reviewing files that changed from the base of the PR and between 76bc7f5 and 68a6f22.

⛔ Files ignored due to path filters (1)
  • go.sum is excluded by !**/*.sum
📒 Files selected for processing (9)
  • go.mod
  • server/android_notification_server.go
  • server/apple_notification_server.go
  • server/apple_notification_test.go
  • server/metrics.go
  • server/metrics_test.go
  • server/push_notification.go
  • server/server.go
  • server/server_test.go
💤 Files with no reviewable changes (1)
  • server/push_notification.go

@lieut-data lieut-data requested review from carlisgg and enahum June 9, 2026 21:47

@enahum enahum left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Going to approve, but as I said in the comment, perhaps a good opportunity to drop appVersion.

Yes, the server and mobile has it in the prefix, but that should not affect anything here other than parsing the Platform which will remain


data := payload.NewPayload()
if msg.Badge == 0 && msg.Type == PushTypeClear && msg.AppVersion > 1 {
if msg.Badge == 0 && msg.Type == model.PushTypeClear && appVersion > 1 {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I think we can drop it, at this point after 4 years no one should be using v1 off the app. I'd say definitely drop it

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants