Skip to content

Commit 12cf46b

Browse files
authored
Merge pull request #7394 from TheThingsNetwork/feature/user-notification-preferences
Add user email notification preferences feature (merge feature branch into version branch)
2 parents 4c593a4 + b9935cc commit 12cf46b

File tree

65 files changed

+2678
-1184
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

65 files changed

+2678
-1184
lines changed

CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,9 @@ For details about compatibility between different releases, see the **Commitment
1111

1212
### Added
1313

14+
- Support user email notification preferences.
15+
- This requires a database migration.
16+
1417
### Changed
1518

1619
### Deprecated

api/buf.yaml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ lint:
3131
- ttn/lorawan/v3/messages.proto
3232
- ttn/lorawan/v3/metadata.proto
3333
- ttn/lorawan/v3/rights.proto
34+
- ttn/lorawan/v3/notification_service.proto
3435
ENUM_VALUE_UPPER_SNAKE_CASE:
3536
- ttn/lorawan/v3/rights.proto
3637
ENUM_ZERO_VALUE_SUFFIX:

api/ttn/lorawan/v3/api.md

Lines changed: 42 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -667,6 +667,7 @@
667667
- [Message `UpdateNotificationStatusRequest`](#ttn.lorawan.v3.UpdateNotificationStatusRequest)
668668
- [Enum `NotificationReceiver`](#ttn.lorawan.v3.NotificationReceiver)
669669
- [Enum `NotificationStatus`](#ttn.lorawan.v3.NotificationStatus)
670+
- [Enum `NotificationType`](#ttn.lorawan.v3.NotificationType)
670671
- [Service `NotificationService`](#ttn.lorawan.v3.NotificationService)
671672
- [File `ttn/lorawan/v3/oauth.proto`](#ttn/lorawan/v3/oauth.proto)
672673
- [Message `ListOAuthAccessTokensRequest`](#ttn.lorawan.v3.ListOAuthAccessTokensRequest)
@@ -798,6 +799,7 @@
798799
- [Message `DeleteInvitationRequest`](#ttn.lorawan.v3.DeleteInvitationRequest)
799800
- [Message `DeleteUserAPIKeyRequest`](#ttn.lorawan.v3.DeleteUserAPIKeyRequest)
800801
- [Message `DeleteUserBookmarkRequest`](#ttn.lorawan.v3.DeleteUserBookmarkRequest)
802+
- [Message `EmailNotificationPreferences`](#ttn.lorawan.v3.EmailNotificationPreferences)
801803
- [Message `GetUserAPIKeyRequest`](#ttn.lorawan.v3.GetUserAPIKeyRequest)
802804
- [Message `GetUserRequest`](#ttn.lorawan.v3.GetUserRequest)
803805
- [Message `Invitation`](#ttn.lorawan.v3.Invitation)
@@ -9488,7 +9490,7 @@ The NsRelayConfigurationService provides configuration management capabilities f
94889490
| Field | Validations |
94899491
| ----- | ----------- |
94909492
| `entity_ids` | <p>`message.required`: `true`</p> |
9491-
| `notification_type` | <p>`string.min_len`: `1`</p><p>`string.max_len`: `100`</p> |
9493+
| `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> |
94929494
| `receivers` | <p>`repeated.min_items`: `1`</p><p>`repeated.unique`: `true`</p><p>`repeated.items.enum.defined_only`: `true`</p> |
94939495

94949496
### <a name="ttn.lorawan.v3.CreateNotificationResponse">Message `CreateNotificationResponse`</a>
@@ -9541,14 +9543,20 @@ The NsRelayConfigurationService provides configuration management capabilities f
95419543
| `id` | [`string`](#string) | | The immutable ID of the notification. Generated by the server. |
95429544
| `created_at` | [`google.protobuf.Timestamp`](#google.protobuf.Timestamp) | | The time when the notification was triggered. |
95439545
| `entity_ids` | [`EntityIdentifiers`](#ttn.lorawan.v3.EntityIdentifiers) | | The entity this notification is about. |
9544-
| `notification_type` | [`string`](#string) | | The type of this notification. |
9546+
| `notification_type` | [`string`](#string) | | The type of this notification. TODO: Replace with type NotificationType in v4 https://github.com/TheThingsNetwork/lorawan-stack/issues/7384. |
95459547
| `data` | [`google.protobuf.Any`](#google.protobuf.Any) | | The data related to the notification. |
95469548
| `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. |
95479549
| `receivers` | [`NotificationReceiver`](#ttn.lorawan.v3.NotificationReceiver) | repeated | Relation of the notification receiver to the entity. |
95489550
| `email` | [`bool`](#bool) | | Whether an email was sent for the notification. |
95499551
| `status` | [`NotificationStatus`](#ttn.lorawan.v3.NotificationStatus) | | The status of the notification. |
95509552
| `status_updated_at` | [`google.protobuf.Timestamp`](#google.protobuf.Timestamp) | | The time when the notification status was updated. |
95519553

9554+
#### Field Rules
9555+
9556+
| Field | Validations |
9557+
| ----- | ----------- |
9558+
| `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> |
9559+
95529560
### <a name="ttn.lorawan.v3.UpdateNotificationStatusRequest">Message `UpdateNotificationStatusRequest`</a>
95539561

95549562
| Field | Type | Label | Description |
@@ -9582,6 +9590,23 @@ The NsRelayConfigurationService provides configuration management capabilities f
95829590
| `NOTIFICATION_STATUS_SEEN` | 1 | |
95839591
| `NOTIFICATION_STATUS_ARCHIVED` | 2 | |
95849592

9593+
### <a name="ttn.lorawan.v3.NotificationType">Enum `NotificationType`</a>
9594+
9595+
| Name | Number | Description |
9596+
| ---- | ------ | ----------- |
9597+
| `UNKNOWN` | 0 | |
9598+
| `API_KEY_CREATED` | 1 | |
9599+
| `API_KEY_CHANGED` | 2 | |
9600+
| `CLIENT_REQUESTED` | 3 | |
9601+
| `COLLABORATOR_CHANGED` | 4 | |
9602+
| `ENTITY_STATE_CHANGED` | 5 | |
9603+
| `INVITATION` | 6 | |
9604+
| `LOGIN_TOKEN` | 7 | |
9605+
| `PASSWORD_CHANGED` | 8 | |
9606+
| `TEMPORARY_PASSWORD` | 9 | |
9607+
| `USER_REQUESTED` | 10 | |
9608+
| `VALIDATE` | 11 | |
9609+
95859610
### <a name="ttn.lorawan.v3.NotificationService">Service `NotificationService`</a>
95869611

95879612
The NotificationService is used to send notifications.
@@ -11348,6 +11373,20 @@ Secret contains a secret value. It also contains the ID of the Encryption key us
1134811373
| `user_ids` | <p>`message.required`: `true`</p> |
1134911374
| `entity_ids` | <p>`message.required`: `true`</p> |
1135011375

11376+
### <a name="ttn.lorawan.v3.EmailNotificationPreferences">Message `EmailNotificationPreferences`</a>
11377+
11378+
EmailNotificationPreferences is the message that defines the types of notifications for which the user wants to receive an email.
11379+
11380+
| Field | Type | Label | Description |
11381+
| ----- | ---- | ----- | ----------- |
11382+
| `types` | [`NotificationType`](#ttn.lorawan.v3.NotificationType) | repeated | |
11383+
11384+
#### Field Rules
11385+
11386+
| Field | Validations |
11387+
| ----- | ----------- |
11388+
| `types` | <p>`repeated.unique`: `true`</p><p>`repeated.items.enum.defined_only`: `true`</p> |
11389+
1135111390
### <a name="ttn.lorawan.v3.GetUserAPIKeyRequest">Message `GetUserAPIKeyRequest`</a>
1135211391

1135311392
| Field | Type | Label | Description |
@@ -11583,6 +11622,7 @@ User is the message that defines a user on the network.
1158311622
| `temporary_password_expires_at` | [`google.protobuf.Timestamp`](#google.protobuf.Timestamp) | | |
1158411623
| `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. |
1158511624
| `console_preferences` | [`UserConsolePreferences`](#ttn.lorawan.v3.UserConsolePreferences) | | Console preferences contains the user's preferences regarding the behavior of the Console. |
11625+
| `email_notification_preferences` | [`EmailNotificationPreferences`](#ttn.lorawan.v3.EmailNotificationPreferences) | | next: 27 |
1158611626

1158711627
#### Field Rules
1158811628

api/ttn/lorawan/v3/api.swagger.json

Lines changed: 39 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22703,6 +22703,18 @@
2270322703
"default": "DOWNLINK_PATH_CONSTRAINT_NONE",
2270422704
"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."
2270522705
},
22706+
"v3EmailNotificationPreferences": {
22707+
"type": "object",
22708+
"properties": {
22709+
"types": {
22710+
"type": "array",
22711+
"items": {
22712+
"$ref": "#/definitions/v3NotificationType"
22713+
}
22714+
}
22715+
},
22716+
"description": "EmailNotificationPreferences is the message that defines the types of notifications for which the user wants to receive an email."
22717+
},
2270622718
"v3EmailValidation": {
2270722719
"type": "object",
2270822720
"properties": {
@@ -27098,7 +27110,7 @@
2709827110
},
2709927111
"notification_type": {
2710027112
"type": "string",
27101-
"description": "The type of this notification."
27113+
"description": "The type of this notification.\nTODO: Replace with type NotificationType in v4 https://github.com/TheThingsNetwork/lorawan-stack/issues/7384."
2710227114
},
2710327115
"data": {
2710427116
"$ref": "#/definitions/protobufAny",
@@ -27150,6 +27162,24 @@
2715027162
],
2715127163
"default": "NOTIFICATION_STATUS_UNSEEN"
2715227164
},
27165+
"v3NotificationType": {
27166+
"type": "string",
27167+
"enum": [
27168+
"UNKNOWN",
27169+
"API_KEY_CREATED",
27170+
"API_KEY_CHANGED",
27171+
"CLIENT_REQUESTED",
27172+
"COLLABORATOR_CHANGED",
27173+
"ENTITY_STATE_CHANGED",
27174+
"INVITATION",
27175+
"LOGIN_TOKEN",
27176+
"PASSWORD_CHANGED",
27177+
"TEMPORARY_PASSWORD",
27178+
"USER_REQUESTED",
27179+
"VALIDATE"
27180+
],
27181+
"default": "UNKNOWN"
27182+
},
2715327183
"v3NsEndDeviceRegistrySetBody": {
2715427184
"type": "object",
2715527185
"properties": {
@@ -29267,6 +29297,10 @@
2926729297
"console_preferences": {
2926829298
"$ref": "#/definitions/v3UserConsolePreferences",
2926929299
"description": "Console preferences contains the user's preferences regarding the behavior of the Console."
29300+
},
29301+
"email_notification_preferences": {
29302+
"$ref": "#/definitions/v3EmailNotificationPreferences",
29303+
"title": "next: 27"
2927029304
}
2927129305
},
2927229306
"description": "User is the message that defines a user on the network."
@@ -29529,6 +29563,10 @@
2952929563
"console_preferences": {
2953029564
"$ref": "#/definitions/v3UserConsolePreferences",
2953129565
"description": "Console preferences contains the user's preferences regarding the behavior of the Console."
29566+
},
29567+
"email_notification_preferences": {
29568+
"$ref": "#/definitions/v3EmailNotificationPreferences",
29569+
"title": "next: 27"
2953229570
}
2953329571
},
2953429572
"description": "User is the message that defines a user on the network."

api/ttn/lorawan/v3/notification_service.proto

Lines changed: 50 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,23 @@ message Notification {
4040
EntityIdentifiers entity_ids = 3;
4141

4242
// The type of this notification.
43-
string notification_type = 4;
43+
// TODO: Replace with type NotificationType in v4 https://github.com/TheThingsNetwork/lorawan-stack/issues/7384.
44+
string notification_type = 4 [(validate.rules).string = {
45+
in: [
46+
"unknown",
47+
"api_key_created",
48+
"api_key_changed",
49+
"client_requested",
50+
"collaborator_changed",
51+
"entity_state_changed",
52+
"invitation",
53+
"login_token",
54+
"password_changed",
55+
"temporary_password",
56+
"user_requested",
57+
"validate"
58+
]
59+
}];
4460

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

5975
// Whether an email was sent for the notification.
60-
bool email = 9;
76+
bool email = 9 [deprecated = true];
6177

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

85+
enum NotificationType {
86+
option (thethings.json.enum) = {marshal_as_string: true};
87+
88+
UNKNOWN = 0;
89+
API_KEY_CREATED = 1;
90+
API_KEY_CHANGED = 2;
91+
CLIENT_REQUESTED = 3;
92+
COLLABORATOR_CHANGED = 4;
93+
ENTITY_STATE_CHANGED = 5;
94+
INVITATION = 6;
95+
LOGIN_TOKEN = 7;
96+
PASSWORD_CHANGED = 8;
97+
TEMPORARY_PASSWORD = 9;
98+
USER_REQUESTED = 10;
99+
VALIDATE = 11;
100+
}
101+
69102
enum NotificationReceiver {
70103
option (thethings.json.enum) = {
71104
marshal_as_string: true,
@@ -104,8 +137,20 @@ message CreateNotificationRequest {
104137

105138
// The type of this notification.
106139
string notification_type = 2 [(validate.rules).string = {
107-
min_len: 1,
108-
max_len: 100,
140+
in: [
141+
"unknown",
142+
"api_key_created",
143+
"api_key_changed",
144+
"client_requested",
145+
"collaborator_changed",
146+
"entity_state_changed",
147+
"invitation",
148+
"login_token",
149+
"password_changed",
150+
"temporary_password",
151+
"user_requested",
152+
"validate"
153+
]
109154
}];
110155

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

126171
// Whether an email should be sent for the notification.
127-
bool email = 6;
172+
bool email = 6 [deprecated = true];
128173
}
129174

130175
message CreateNotificationResponse {

api/ttn/lorawan/v3/user.proto

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ import "thethings/json/annotations.proto";
2323
import "ttn/lorawan/v3/contact_info.proto";
2424
import "ttn/lorawan/v3/enums.proto";
2525
import "ttn/lorawan/v3/identifiers.proto";
26+
import "ttn/lorawan/v3/notification_service.proto";
2627
import "ttn/lorawan/v3/picture.proto";
2728
import "ttn/lorawan/v3/rights.proto";
2829
import "validate/validate.proto";
@@ -62,6 +63,16 @@ enum DashboardLayout {
6263
DASHBOARD_LAYOUT_GRID = 2;
6364
}
6465

66+
// EmailNotificationPreferences is the message that defines the types of notifications for which the user wants to receive an email.
67+
message EmailNotificationPreferences {
68+
repeated NotificationType types = 1 [(validate.rules).repeated = {
69+
unique: true,
70+
items: {
71+
enum: {defined_only: true}
72+
}
73+
}];
74+
}
75+
6576
// UserConsolePreferences is the message that defines the user preferences for the Console.
6677
message UserConsolePreferences {
6778
option (thethings.flags.message) = {
@@ -306,7 +317,8 @@ message User {
306317
// Console preferences contains the user's preferences regarding the behavior of the Console.
307318
UserConsolePreferences console_preferences = 25;
308319

309-
// next: 26
320+
EmailNotificationPreferences email_notification_preferences = 26;
321+
// next: 27
310322
}
311323

312324
message Users {

pkg/email/dir/dir_test.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import (
2020
"testing"
2121

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

4243
err = mailer.Send(&email.Message{
43-
TemplateName: "irrelevant",
44+
TemplateName: ttnpb.GetNotificationTypeString(ttnpb.NotificationType_UNKNOWN),
4445
RecipientName: "John Doe",
4546
RecipientAddress: "john.doe@example.com",
4647
Subject: "Email Subject",

pkg/email/email_test.go

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ func TestEmail(t *testing.T) {
4040
registry := email.NewTemplateRegistry()
4141

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

5555
registry.RegisterTemplate(welcomeEmailTemplate)
56-
57-
a.So(registry.RegisteredTemplates(), should.Contain, "welcome")
58-
returnedTemplate := registry.GetTemplate(ctx, "welcome")
56+
a.So(registry.RegisteredTemplates(), should.Contain, "unknown")
57+
returnedTemplate := registry.GetTemplate(ctx, ttnpb.GetNotificationTypeString(ttnpb.NotificationType_UNKNOWN))
5958

6059
for i, template := range []*email.Template{welcomeEmailTemplate, returnedTemplate} {
6160
template := template

pkg/email/sendgrid/sendgrid_test.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ import (
2121
"github.com/smarty/assertions"
2222
"go.thethings.network/lorawan-stack/v3/pkg/email"
2323
"go.thethings.network/lorawan-stack/v3/pkg/log"
24+
"go.thethings.network/lorawan-stack/v3/pkg/ttnpb"
2425
"go.thethings.network/lorawan-stack/v3/pkg/util/test"
2526
"go.thethings.network/lorawan-stack/v3/pkg/util/test/assertions/should"
2627
)
@@ -46,7 +47,7 @@ func TestSendGrid(t *testing.T) {
4647
a.So(err, should.BeNil)
4748

4849
err = sg.Send(&email.Message{
49-
TemplateName: "test",
50+
TemplateName: ttnpb.GetNotificationTypeString(ttnpb.NotificationType_UNKNOWN),
5051
RecipientName: "John Doe",
5152
RecipientAddress: "john.doe@example.com",
5253
Subject: "Testing SendGrid",

pkg/email/smtp/smtp_test.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ import (
2222
"github.com/smarty/assertions"
2323
"go.thethings.network/lorawan-stack/v3/pkg/email"
2424
"go.thethings.network/lorawan-stack/v3/pkg/log"
25+
"go.thethings.network/lorawan-stack/v3/pkg/ttnpb"
2526
"go.thethings.network/lorawan-stack/v3/pkg/util/test"
2627
"go.thethings.network/lorawan-stack/v3/pkg/util/test/assertions/should"
2728
)
@@ -64,7 +65,7 @@ func TestSMTP(t *testing.T) {
6465
a.So(err, should.BeNil)
6566

6667
mail := &email.Message{
67-
TemplateName: "test",
68+
TemplateName: ttnpb.GetNotificationTypeString(ttnpb.NotificationType_UNKNOWN),
6869
RecipientName: "John Doe",
6970
RecipientAddress: "john.doe@example.com",
7071
Subject: "Testing SMTP",

0 commit comments

Comments
 (0)