Skip to content

Commit

Permalink
Merge pull request #7349 from TheThingsNetwork/feature/notification-p…
Browse files Browse the repository at this point in the history
…references

Support user notification preferences (backend)
  • Loading branch information
ryaplots authored Nov 15, 2024
2 parents 1cc63fb + 7b5e18f commit 6dcf0be
Show file tree
Hide file tree
Showing 56 changed files with 2,263 additions and 1,146 deletions.
1 change: 1 addition & 0 deletions api/buf.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ lint:
- ttn/lorawan/v3/messages.proto
- ttn/lorawan/v3/metadata.proto
- ttn/lorawan/v3/rights.proto
- ttn/lorawan/v3/notification_service.proto
ENUM_VALUE_UPPER_SNAKE_CASE:
- ttn/lorawan/v3/rights.proto
ENUM_ZERO_VALUE_SUFFIX:
Expand Down
44 changes: 42 additions & 2 deletions api/ttn/lorawan/v3/api.md
Original file line number Diff line number Diff line change
Expand Up @@ -667,6 +667,7 @@
- [Message `UpdateNotificationStatusRequest`](#ttn.lorawan.v3.UpdateNotificationStatusRequest)
- [Enum `NotificationReceiver`](#ttn.lorawan.v3.NotificationReceiver)
- [Enum `NotificationStatus`](#ttn.lorawan.v3.NotificationStatus)
- [Enum `NotificationType`](#ttn.lorawan.v3.NotificationType)
- [Service `NotificationService`](#ttn.lorawan.v3.NotificationService)
- [File `ttn/lorawan/v3/oauth.proto`](#ttn/lorawan/v3/oauth.proto)
- [Message `ListOAuthAccessTokensRequest`](#ttn.lorawan.v3.ListOAuthAccessTokensRequest)
Expand Down Expand Up @@ -798,6 +799,7 @@
- [Message `DeleteInvitationRequest`](#ttn.lorawan.v3.DeleteInvitationRequest)
- [Message `DeleteUserAPIKeyRequest`](#ttn.lorawan.v3.DeleteUserAPIKeyRequest)
- [Message `DeleteUserBookmarkRequest`](#ttn.lorawan.v3.DeleteUserBookmarkRequest)
- [Message `EmailNotificationPreferences`](#ttn.lorawan.v3.EmailNotificationPreferences)
- [Message `GetUserAPIKeyRequest`](#ttn.lorawan.v3.GetUserAPIKeyRequest)
- [Message `GetUserRequest`](#ttn.lorawan.v3.GetUserRequest)
- [Message `Invitation`](#ttn.lorawan.v3.Invitation)
Expand Down Expand Up @@ -9486,7 +9488,7 @@ The NsRelayConfigurationService provides configuration management capabilities f
| Field | Validations |
| ----- | ----------- |
| `entity_ids` | <p>`message.required`: `true`</p> |
| `notification_type` | <p>`string.min_len`: `1`</p><p>`string.max_len`: `100`</p> |
| `notification_type` | <p>`string.in`: `[unknown api_key_created api_key_changed client_requested collaborator_changed entity_state_changed invitation login_token password_changed temporary_password user_requested validate]`</p> |
| `receivers` | <p>`repeated.min_items`: `1`</p><p>`repeated.unique`: `true`</p><p>`repeated.items.enum.defined_only`: `true`</p> |

### <a name="ttn.lorawan.v3.CreateNotificationResponse">Message `CreateNotificationResponse`</a>
Expand Down Expand Up @@ -9539,14 +9541,20 @@ The NsRelayConfigurationService provides configuration management capabilities f
| `id` | [`string`](#string) | | The immutable ID of the notification. Generated by the server. |
| `created_at` | [`google.protobuf.Timestamp`](#google.protobuf.Timestamp) | | The time when the notification was triggered. |
| `entity_ids` | [`EntityIdentifiers`](#ttn.lorawan.v3.EntityIdentifiers) | | The entity this notification is about. |
| `notification_type` | [`string`](#string) | | The type of this notification. |
| `notification_type` | [`string`](#string) | | The type of this notification. TODO: Replace with type NotificationType in v4 https://github.com/TheThingsNetwork/lorawan-stack/issues/7384. |
| `data` | [`google.protobuf.Any`](#google.protobuf.Any) | | The data related to the notification. |
| `sender_ids` | [`UserIdentifiers`](#ttn.lorawan.v3.UserIdentifiers) | | If the notification was triggered by a user action, this contains the identifiers of the user that triggered the notification. |
| `receivers` | [`NotificationReceiver`](#ttn.lorawan.v3.NotificationReceiver) | repeated | Relation of the notification receiver to the entity. |
| `email` | [`bool`](#bool) | | Whether an email was sent for the notification. |
| `status` | [`NotificationStatus`](#ttn.lorawan.v3.NotificationStatus) | | The status of the notification. |
| `status_updated_at` | [`google.protobuf.Timestamp`](#google.protobuf.Timestamp) | | The time when the notification status was updated. |

#### Field Rules

| Field | Validations |
| ----- | ----------- |
| `notification_type` | <p>`string.in`: `[unknown api_key_created api_key_changed client_requested collaborator_changed entity_state_changed invitation login_token password_changed temporary_password user_requested validate]`</p> |

### <a name="ttn.lorawan.v3.UpdateNotificationStatusRequest">Message `UpdateNotificationStatusRequest`</a>

| Field | Type | Label | Description |
Expand Down Expand Up @@ -9580,6 +9588,23 @@ The NsRelayConfigurationService provides configuration management capabilities f
| `NOTIFICATION_STATUS_SEEN` | 1 | |
| `NOTIFICATION_STATUS_ARCHIVED` | 2 | |

### <a name="ttn.lorawan.v3.NotificationType">Enum `NotificationType`</a>

| Name | Number | Description |
| ---- | ------ | ----------- |
| `UNKNOWN` | 0 | |
| `API_KEY_CREATED` | 1 | |
| `API_KEY_CHANGED` | 2 | |
| `CLIENT_REQUESTED` | 3 | |
| `COLLABORATOR_CHANGED` | 4 | |
| `ENTITY_STATE_CHANGED` | 5 | |
| `INVITATION` | 6 | |
| `LOGIN_TOKEN` | 7 | |
| `PASSWORD_CHANGED` | 8 | |
| `TEMPORARY_PASSWORD` | 9 | |
| `USER_REQUESTED` | 10 | |
| `VALIDATE` | 11 | |

### <a name="ttn.lorawan.v3.NotificationService">Service `NotificationService`</a>

The NotificationService is used to send notifications.
Expand Down Expand Up @@ -11346,6 +11371,20 @@ Secret contains a secret value. It also contains the ID of the Encryption key us
| `user_ids` | <p>`message.required`: `true`</p> |
| `entity_ids` | <p>`message.required`: `true`</p> |

### <a name="ttn.lorawan.v3.EmailNotificationPreferences">Message `EmailNotificationPreferences`</a>

EmailNotificationPreferences is the message that defines the types of notifications for which the user wants to receive an email.

| Field | Type | Label | Description |
| ----- | ---- | ----- | ----------- |
| `types` | [`NotificationType`](#ttn.lorawan.v3.NotificationType) | repeated | |

#### Field Rules

| Field | Validations |
| ----- | ----------- |
| `types` | <p>`repeated.unique`: `true`</p><p>`repeated.items.enum.defined_only`: `true`</p> |

### <a name="ttn.lorawan.v3.GetUserAPIKeyRequest">Message `GetUserAPIKeyRequest`</a>

| Field | Type | Label | Description |
Expand Down Expand Up @@ -11581,6 +11620,7 @@ User is the message that defines a user on the network.
| `temporary_password_expires_at` | [`google.protobuf.Timestamp`](#google.protobuf.Timestamp) | | |
| `profile_picture` | [`Picture`](#ttn.lorawan.v3.Picture) | | A profile picture for the user. This information is public and can be seen by any authenticated user in the network. |
| `console_preferences` | [`UserConsolePreferences`](#ttn.lorawan.v3.UserConsolePreferences) | | Console preferences contains the user's preferences regarding the behavior of the Console. |
| `email_notification_preferences` | [`EmailNotificationPreferences`](#ttn.lorawan.v3.EmailNotificationPreferences) | | next: 27 |

#### Field Rules

Expand Down
40 changes: 39 additions & 1 deletion api/ttn/lorawan/v3/api.swagger.json
Original file line number Diff line number Diff line change
Expand Up @@ -22692,6 +22692,18 @@
"default": "DOWNLINK_PATH_CONSTRAINT_NONE",
"description": " - DOWNLINK_PATH_CONSTRAINT_NONE: Indicates that the gateway can be selected for downlink without constraints by the Network Server.\n - DOWNLINK_PATH_CONSTRAINT_PREFER_OTHER: Indicates that the gateway can be selected for downlink only if no other or better gateway can be selected.\n - DOWNLINK_PATH_CONSTRAINT_NEVER: Indicates that this gateway will never be selected for downlink, even if that results in no available downlink path."
},
"v3EmailNotificationPreferences": {
"type": "object",
"properties": {
"types": {
"type": "array",
"items": {
"$ref": "#/definitions/v3NotificationType"
}
}
},
"description": "EmailNotificationPreferences is the message that defines the types of notifications for which the user wants to receive an email."
},
"v3EmailValidation": {
"type": "object",
"properties": {
Expand Down Expand Up @@ -27087,7 +27099,7 @@
},
"notification_type": {
"type": "string",
"description": "The type of this notification."
"description": "The type of this notification.\nTODO: Replace with type NotificationType in v4 https://github.com/TheThingsNetwork/lorawan-stack/issues/7384."
},
"data": {
"$ref": "#/definitions/protobufAny",
Expand Down Expand Up @@ -27139,6 +27151,24 @@
],
"default": "NOTIFICATION_STATUS_UNSEEN"
},
"v3NotificationType": {
"type": "string",
"enum": [
"UNKNOWN",
"API_KEY_CREATED",
"API_KEY_CHANGED",
"CLIENT_REQUESTED",
"COLLABORATOR_CHANGED",
"ENTITY_STATE_CHANGED",
"INVITATION",
"LOGIN_TOKEN",
"PASSWORD_CHANGED",
"TEMPORARY_PASSWORD",
"USER_REQUESTED",
"VALIDATE"
],
"default": "UNKNOWN"
},
"v3NsEndDeviceRegistrySetBody": {
"type": "object",
"properties": {
Expand Down Expand Up @@ -29247,6 +29277,10 @@
"console_preferences": {
"$ref": "#/definitions/v3UserConsolePreferences",
"description": "Console preferences contains the user's preferences regarding the behavior of the Console."
},
"email_notification_preferences": {
"$ref": "#/definitions/v3EmailNotificationPreferences",
"title": "next: 27"
}
},
"description": "User is the message that defines a user on the network."
Expand Down Expand Up @@ -29506,6 +29540,10 @@
"console_preferences": {
"$ref": "#/definitions/v3UserConsolePreferences",
"description": "Console preferences contains the user's preferences regarding the behavior of the Console."
},
"email_notification_preferences": {
"$ref": "#/definitions/v3EmailNotificationPreferences",
"title": "next: 27"
}
},
"description": "User is the message that defines a user on the network."
Expand Down
55 changes: 50 additions & 5 deletions api/ttn/lorawan/v3/notification_service.proto
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,23 @@ message Notification {
EntityIdentifiers entity_ids = 3;

// The type of this notification.
string notification_type = 4;
// TODO: Replace with type NotificationType in v4 https://github.com/TheThingsNetwork/lorawan-stack/issues/7384.
string notification_type = 4 [(validate.rules).string = {
in: [
"unknown",
"api_key_created",
"api_key_changed",
"client_requested",
"collaborator_changed",
"entity_state_changed",
"invitation",
"login_token",
"password_changed",
"temporary_password",
"user_requested",
"validate"
]
}];

// The data related to the notification.
google.protobuf.Any data = 5;
Expand All @@ -57,7 +73,7 @@ message Notification {
repeated NotificationReceiver receivers = 8;

// Whether an email was sent for the notification.
bool email = 9;
bool email = 9 [deprecated = true];

// The status of the notification.
NotificationStatus status = 10;
Expand All @@ -66,6 +82,23 @@ message Notification {
google.protobuf.Timestamp status_updated_at = 11;
}

enum NotificationType {
option (thethings.json.enum) = {marshal_as_string: true};

UNKNOWN = 0;
API_KEY_CREATED = 1;
API_KEY_CHANGED = 2;
CLIENT_REQUESTED = 3;
COLLABORATOR_CHANGED = 4;
ENTITY_STATE_CHANGED = 5;
INVITATION = 6;
LOGIN_TOKEN = 7;
PASSWORD_CHANGED = 8;
TEMPORARY_PASSWORD = 9;
USER_REQUESTED = 10;
VALIDATE = 11;
}

enum NotificationReceiver {
option (thethings.json.enum) = {
marshal_as_string: true,
Expand Down Expand Up @@ -104,8 +137,20 @@ message CreateNotificationRequest {

// The type of this notification.
string notification_type = 2 [(validate.rules).string = {
min_len: 1,
max_len: 100,
in: [
"unknown",
"api_key_created",
"api_key_changed",
"client_requested",
"collaborator_changed",
"entity_state_changed",
"invitation",
"login_token",
"password_changed",
"temporary_password",
"user_requested",
"validate"
]
}];

// The data related to the notification.
Expand All @@ -124,7 +169,7 @@ message CreateNotificationRequest {
}];

// Whether an email should be sent for the notification.
bool email = 6;
bool email = 6 [deprecated = true];
}

message CreateNotificationResponse {
Expand Down
14 changes: 13 additions & 1 deletion api/ttn/lorawan/v3/user.proto
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import "thethings/json/annotations.proto";
import "ttn/lorawan/v3/contact_info.proto";
import "ttn/lorawan/v3/enums.proto";
import "ttn/lorawan/v3/identifiers.proto";
import "ttn/lorawan/v3/notification_service.proto";
import "ttn/lorawan/v3/picture.proto";
import "ttn/lorawan/v3/rights.proto";
import "validate/validate.proto";
Expand Down Expand Up @@ -54,6 +55,16 @@ enum DashboardLayout {
DASHBOARD_LAYOUT_GRID = 2;
}

// EmailNotificationPreferences is the message that defines the types of notifications for which the user wants to receive an email.
message EmailNotificationPreferences {
repeated NotificationType types = 1 [(validate.rules).repeated = {
unique: true,
items: {
enum: {defined_only: true}
}
}];
}

// UserConsolePreferences is the message that defines the user preferences for the Console.
message UserConsolePreferences {
option (thethings.flags.message) = {
Expand Down Expand Up @@ -288,7 +299,8 @@ message User {
// Console preferences contains the user's preferences regarding the behavior of the Console.
UserConsolePreferences console_preferences = 25;

// next: 26
EmailNotificationPreferences email_notification_preferences = 26;
// next: 27
}

message Users {
Expand Down
3 changes: 2 additions & 1 deletion pkg/email/dir/dir_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import (
"testing"

"go.thethings.network/lorawan-stack/v3/pkg/email"
"go.thethings.network/lorawan-stack/v3/pkg/ttnpb"
"go.thethings.network/lorawan-stack/v3/pkg/util/test"
"go.thethings.network/lorawan-stack/v3/pkg/util/test/assertions/should"
)
Expand All @@ -40,7 +41,7 @@ func TestMailDir(t *testing.T) {
a.So(err, should.BeNil)

err = mailer.Send(&email.Message{
TemplateName: "irrelevant",
TemplateName: ttnpb.GetNotificationTypeString(ttnpb.NotificationType_UNKNOWN),
RecipientName: "John Doe",
RecipientAddress: "john.doe@example.com",
Subject: "Email Subject",
Expand Down
7 changes: 3 additions & 4 deletions pkg/email/email_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ func TestEmail(t *testing.T) {
registry := email.NewTemplateRegistry()

welcomeEmailTemplate, err := email.NewTemplateFS(
os.DirFS("testdata"), "welcome",
os.DirFS("testdata"), ttnpb.GetNotificationTypeString(ttnpb.NotificationType_UNKNOWN),
email.FSTemplate{
SubjectTemplate: "Welcome to {{ .Network.Name }}",
HTMLTemplateBaseFile: "base.html",
Expand All @@ -53,9 +53,8 @@ func TestEmail(t *testing.T) {
a.So(err, should.BeNil)

registry.RegisterTemplate(welcomeEmailTemplate)

a.So(registry.RegisteredTemplates(), should.Contain, "welcome")
returnedTemplate := registry.GetTemplate(ctx, "welcome")
a.So(registry.RegisteredTemplates(), should.Contain, "unknown")
returnedTemplate := registry.GetTemplate(ctx, ttnpb.GetNotificationTypeString(ttnpb.NotificationType_UNKNOWN))

for i, template := range []*email.Template{welcomeEmailTemplate, returnedTemplate} {
template := template
Expand Down
3 changes: 2 additions & 1 deletion pkg/email/sendgrid/sendgrid_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import (
"github.com/smarty/assertions"
"go.thethings.network/lorawan-stack/v3/pkg/email"
"go.thethings.network/lorawan-stack/v3/pkg/log"
"go.thethings.network/lorawan-stack/v3/pkg/ttnpb"
"go.thethings.network/lorawan-stack/v3/pkg/util/test"
"go.thethings.network/lorawan-stack/v3/pkg/util/test/assertions/should"
)
Expand All @@ -46,7 +47,7 @@ func TestSendGrid(t *testing.T) {
a.So(err, should.BeNil)

err = sg.Send(&email.Message{
TemplateName: "test",
TemplateName: ttnpb.GetNotificationTypeString(ttnpb.NotificationType_UNKNOWN),
RecipientName: "John Doe",
RecipientAddress: "john.doe@example.com",
Subject: "Testing SendGrid",
Expand Down
3 changes: 2 additions & 1 deletion pkg/email/smtp/smtp_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import (
"github.com/smarty/assertions"
"go.thethings.network/lorawan-stack/v3/pkg/email"
"go.thethings.network/lorawan-stack/v3/pkg/log"
"go.thethings.network/lorawan-stack/v3/pkg/ttnpb"
"go.thethings.network/lorawan-stack/v3/pkg/util/test"
"go.thethings.network/lorawan-stack/v3/pkg/util/test/assertions/should"
)
Expand Down Expand Up @@ -64,7 +65,7 @@ func TestSMTP(t *testing.T) {
a.So(err, should.BeNil)

mail := &email.Message{
TemplateName: "test",
TemplateName: ttnpb.GetNotificationTypeString(ttnpb.NotificationType_UNKNOWN),
RecipientName: "John Doe",
RecipientAddress: "john.doe@example.com",
Subject: "Testing SMTP",
Expand Down
2 changes: 1 addition & 1 deletion pkg/email/template.go
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ func newContextWithTemplateRegistry(parent context.Context, reg TemplateRegistry

// TemplateRegistry keeps track of email templates.
type TemplateRegistry interface {
RegisteredTemplates() []string
RegisteredTemplates() []*ttnpb.NotificationType
GetTemplate(ctx context.Context, name string) *Template
}

Expand Down
Loading

0 comments on commit 6dcf0be

Please sign in to comment.