Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support user notification preferences (backend) #7349

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
95608fb
all: Add NotificationType enum and use it throughout the code
ryaplots Sep 19, 2024
d84361b
is: Add notification preferences migration and user field
ryaplots Oct 1, 2024
22b101e
is: Deprecate email in Notification and createNotification messages
ryaplots Oct 2, 2024
9855737
is: Fix email_notification_preferences migration and store
ryaplots Oct 2, 2024
ddeeb1f
is, console: Fix sending emails and seeing notifications
ryaplots Oct 4, 2024
aea1943
console: Add fe validation schema
ryaplots Oct 7, 2024
a7a59df
is: Fix email_notification_preferences type and migration
ryaplots Oct 7, 2024
0cb6170
is: Make only one request to get the users
ryaplots Oct 7, 2024
76fcdfe
is: Add test case for emailNotificationPreferences
ryaplots Oct 9, 2024
59f675a
is: Remove email preferences check for admins
ryaplots Oct 17, 2024
e7f6095
dev: Rename test templates
ryaplots Oct 23, 2024
7a64e97
is: Add email notification preferences flow test
ryaplots Oct 24, 2024
ed7391b
is: Add test for sending emails if admin
ryaplots Oct 28, 2024
82a4440
is: Fix store path
ryaplots Oct 30, 2024
54b51f1
is: Fix tests
ryaplots Oct 30, 2024
8b03389
is: Add user preferences
ryaplots Oct 30, 2024
dfa32da
is: Improve readability of email preference logic
ryaplots Nov 1, 2024
dd99906
is: Address comments
ryaplots Nov 11, 2024
0c312fc
is: Use string notification type in lowercase
ryaplots Nov 12, 2024
e9d3fa8
is: Add comment
ryaplots Nov 12, 2024
f5f3e6c
is: Fix linitng
ryaplots Nov 12, 2024
82b22e2
is: Fix comment
ryaplots Nov 12, 2024
23c41a0
console: Revert constants
ryaplots Nov 12, 2024
508bd49
is: Add issue in comment
ryaplots Nov 15, 2024
c384401
is: Merge two functions into one and add panic
ryaplots Nov 15, 2024
1d2e413
is: Add missing comment
ryaplots Nov 15, 2024
cb57746
is: Remove panic
ryaplots Nov 15, 2024
7b5e18f
is: Fix panic
ryaplots Nov 15, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading