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

Introduce Notifications CRUD Service #38327

Merged
merged 1 commit into from
Mar 1, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
127 changes: 64 additions & 63 deletions api/gen/proto/go/teleport/notifications/v1/notifications.pb.go

Large diffs are not rendered by default.

779 changes: 251 additions & 528 deletions api/gen/proto/go/teleport/notifications/v1/notifications_service.pb.go

Large diffs are not rendered by default.

Large diffs are not rendered by default.

14 changes: 8 additions & 6 deletions api/proto/teleport/notifications/v1/notifications.proto
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
/*
* Teleport
* Copyright (C) 2023 Gravitational, Inc.
* Copyright (C) 2024 Gravitational, Inc.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
Expand Down Expand Up @@ -34,7 +34,7 @@ message Notification {
string sub_kind = 2;
// version is the resource version.
string version = 3;
// metadata is the notification's metadata. This contains the notification's title, description, labels, and expiry.
// metadata is the notification's metadata. This contains the notification's labels, and expiry. All custom notification metadata should be stored in labels.
teleport.header.v1.Metadata metadata = 4;
// spec is the notification specification.
NotificationSpec spec = 5;
Expand Down Expand Up @@ -146,15 +146,17 @@ message UserLastSeenNotification {
teleport.header.v1.Metadata metadata = 4;
// UserLastSeenNotificationSpec is the user last seen notification item's specification.
UserLastSeenNotificationSpec spec = 5;
// time is the timestamp of this user's last seen notification, it contains the timestamp of the notification which will be dynamically modified.
UserLastSeenNotificationTime time = 6;
reserved 6;
reserved "time";
// status is the timestamp of this user's last seen notification, it contains the timestamp of the notification which will be dynamically modified.
UserLastSeenNotificationStatus status = 7;
}

// UserLastSeenNotificationSpec is a user last seen notification specification.
message UserLastSeenNotificationSpec {}

// UserLastSeenNotificationTime is the timestamp of this user's last seen notification, it contains the timestamp of the notification which will be dynamically modified.
message UserLastSeenNotificationTime {
// UserLastSeenNotificationStatus is the timestamp of this user's last seen notification, it contains the timestamp of the notification which will be dynamically modified.
message UserLastSeenNotificationStatus {
// last_seen_time is the timestamp of the last notification that the user has seen.
google.protobuf.Timestamp last_seen_time = 1;
}
57 changes: 14 additions & 43 deletions api/proto/teleport/notifications/v1/notifications_service.proto
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
/*
* Teleport
* Copyright (C) 2023 Gravitational, Inc.
* Copyright (C) 2024 Gravitational, Inc.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
Expand Down Expand Up @@ -31,31 +31,26 @@ service NotificationService {
rpc CreateUserNotification(CreateUserNotificationRequest) returns (Notification);
// DeleteUserNotification deletes a user-specific notification.
rpc DeleteUserNotification(DeleteUserNotificationRequest) returns (google.protobuf.Empty);
// ListUserNotifications returns a page of user-specific notifications.
rpc ListUserNotifications(ListUserNotificationsRequest) returns (ListUserNotificationsResponse);

// CreateGlobalNotification creates a global notification.
rpc CreateGlobalNotification(CreateGlobalNotificationRequest) returns (GlobalNotification);
// DeleteGlobalNotification deletes a global notification.
rpc DeleteGlobalNotification(DeleteGlobalNotificationRequest) returns (google.protobuf.Empty);
// ListGlobalNotifications returns a page of global notifications.
rpc ListGlobalNotifications(ListGlobalNotificationsRequest) returns (ListGlobalNotificationsResponse);
rudream marked this conversation as resolved.
Show resolved Hide resolved

// CreateUserNotificationState creates a user notification state which records whether a user has clicked on or dismissed a notification.
rpc CreateUserNotificationState(CreateUserNotificationStateRequest) returns (UserNotificationState);
// UpdateUserNotificationState updates a user notification state to record whether a user has clicked on or dismissed a notification.
rpc UpdateUserNotificationState(UpdateUserNotificationStateRequest) returns (UserNotificationState);
// ListUserNotificationsRequest is the request for listing a user's notifications, which include user-specific ones as well as global notifications that match them.
rpc ListUserNotifications(ListUserNotificationsRequest) returns (ListUserNotificationsResponse);

// UpsertUserNotificationState creates or updates a user notification state which records whether the user has clicked on or dismissed a notification.
rpc UpsertUserNotificationState(UpsertUserNotificationStateRequest) returns (UserNotificationState);
// DeleteUserNotificationState deletes a user notification state object.
rpc DeleteUserNotificationState(DeleteUserNotificationStateRequest) returns (google.protobuf.Empty);
// ListUserNotificationStates returns a page of user notification states.
// ListUserNotificationStates returns a page of a user's notification states.
rpc ListUserNotificationStates(ListUserNotificationStatesRequest) returns (ListUserNotificationStatesResponse);

// GetUserLastSeenNotification returns a user's last seen notification item.
rpc GetUserLastSeenNotification(GetUserLastSeenNotificationRequest) returns (UserLastSeenNotification);
// CreateUserLastSeenNotification creates a user's last seen notification item.
rpc CreateUserLastSeenNotification(CreateUserLastSeenNotificationRequest) returns (UserLastSeenNotification);
// UpdateUserLastSeenNotification updates a user's last seen notification item.
rpc UpdateUserLastSeenNotification(UpdateUserLastSeenNotificationRequest) returns (UserLastSeenNotification);
// UpsertUserLastSeenNotification creates or updates a user's last seen notification item.
rpc UpsertUserLastSeenNotification(UpsertUserLastSeenNotificationRequest) returns (UserLastSeenNotification);
// DeleteUserLastSeenNotification deletes a user's last seen notification item.
rpc DeleteUserLastSeenNotification(DeleteUserLastSeenNotificationRequest) returns (google.protobuf.Empty);
}
Expand All @@ -76,7 +71,7 @@ message DeleteUserNotificationRequest {
string notification_id = 2;
}

// ListUserNotificationsRequest is the request for listing a user's user-specific notifications.
// ListUserNotificationsRequest is the request for listing a user's notifications, which include user-specific ones as well as global notifications that match them.
message ListUserNotificationsRequest {
// username is the username of the user the notifications to list are for.
string username = 1;
Expand Down Expand Up @@ -106,24 +101,8 @@ message DeleteGlobalNotificationRequest {
string notification_id = 1;
}

// ListGlobalNotificationsRequest is the request for listing global notifications.
message ListGlobalNotificationsRequest {
// page_size is the size of the page to return.
int32 page_size = 1;
// page_token is the next_page_token value returned from a previous ListGlobalNotifications request, if any.
string page_token = 2;
}

// ListGlobalNotificationsResponse is the response from listing global notifications.
message ListGlobalNotificationsResponse {
// global_notifications is the global notification items returned.
repeated GlobalNotification global_notifications = 1;
// next_page_token is the token to retrieve the next page of results, this will be empty if there are no more results.
string next_page_token = 2;
}

// CreateUserNotificationStateRequest is the request for creating a user notification state.
message CreateUserNotificationStateRequest {
// UpsertUserNotificationStateRequest is the request for creating or updating a user notification state.
message UpsertUserNotificationStateRequest {
// username is the username of the user.
string username = 1;
// user_notification_state is the user notification state to create.
Expand Down Expand Up @@ -166,16 +145,8 @@ message GetUserLastSeenNotificationRequest {
string username = 1;
}

// CreateUserLastSeenNotification is the request for creating a user's last seen notification item.
message CreateUserLastSeenNotificationRequest {
// username is the username of the user.
string username = 1;
// user_notification_state is the user last seen notification item to create.
UserLastSeenNotification user_last_seen_notification = 2;
}

// UpdateUserLastSeenNotificationRequest is the request for updating a user's last seen notification.
message UpdateUserLastSeenNotificationRequest {
// UpsertUserLastSeenNotificationRequest is the request for creating or updating a user's last seen notification.
message UpsertUserLastSeenNotificationRequest {
// username is the username of the user.
string username = 1;
// user_notification_state is the udpated user last seen notification item.
Expand Down
9 changes: 9 additions & 0 deletions api/types/constants.go
Original file line number Diff line number Diff line change
Expand Up @@ -503,6 +503,15 @@ const (
// KindSecurityReportCostLimiter const limiter
KindSecurityReportCostLimiter = "security_report_cost_limiter"

// KindNotification is a notification resource.
KindNotification = "notification"
// KindGlobalNotification is a global notification resource.
KindGlobalNotification = "global_notification"
// KindUserLastSeenNotification is a resource which stores the timestamp of a user's last seen notification.
KindUserLastSeenNotification = "user_last_seen_notification"
// KindUserNotificationState is a resource which tracks whether a user has clicked on or dismissed a notification.
KindUserNotificationState = "user_notification_state"

// V7 is the seventh version of resources.
V7 = "v7"

Expand Down
25 changes: 25 additions & 0 deletions lib/services/local/generic/generic_wrapper.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ package generic

import (
"context"
"strings"

"github.com/gravitational/trace"

Expand Down Expand Up @@ -62,6 +63,24 @@ type ServiceWrapper[T types.ResourceMetadata] struct {
service *Service[resourceMetadataAdapter[T]]
}

// WithPrefix will return a service wrapper with the given parts appended to the backend prefix.
func (s ServiceWrapper[T]) WithPrefix(parts ...string) *ServiceWrapper[T] {
rudream marked this conversation as resolved.
Show resolved Hide resolved
if len(parts) == 0 {
return &s
}

return &ServiceWrapper[T]{
service: &Service[resourceMetadataAdapter[T]]{
backend: s.service.backend,
resourceKind: s.service.resourceKind,
pageLimit: s.service.pageLimit,
backendPrefix: strings.Join(append([]string{s.service.backendPrefix}, parts...), string(backend.Separator)),
marshalFunc: s.service.marshalFunc,
unmarshalFunc: s.service.unmarshalFunc,
},
}
}

// UpsertResource upserts a resource.
func (s ServiceWrapper[T]) UpsertResource(ctx context.Context, resource T) (T, error) {
adapter, err := s.service.UpsertResource(ctx, newResourceMetadataAdapter(resource))
Expand Down Expand Up @@ -91,6 +110,12 @@ func (s ServiceWrapper[T]) DeleteResource(ctx context.Context, name string) erro
return trace.Wrap(s.service.DeleteResource(ctx, name))
}

// DeleteAllResources removes all resources.
func (s ServiceWrapper[T]) DeleteAllResources(ctx context.Context) error {
startKey := backend.ExactKey(s.service.backendPrefix)
return trace.Wrap(s.service.backend.DeleteRange(ctx, startKey, backend.RangeEnd(startKey)))
}

// ListResources returns a paginated list of resources.
func (s ServiceWrapper[T]) ListResources(ctx context.Context, pageSize int, pageToken string) ([]T, string, error) {
adapters, nextToken, err := s.service.ListResources(ctx, pageSize, pageToken)
Expand Down
28 changes: 28 additions & 0 deletions lib/services/local/generic/generic_wrapper_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -224,3 +224,31 @@ func TestGenericWrapperCRUD(t *testing.T) {
err = service.DeleteResource(ctx, "doesnotexist")
require.True(t, trace.IsNotFound(err))
}

// TestGenericWrapperWithPrefix tests the withPrefix method of the generic service wrapper.
func TestGenericWrapperWithPrefix(t *testing.T) {
ctx := context.Background()

memBackend, err := memory.New(memory.Config{
Context: ctx,
Clock: clockwork.NewFakeClock(),
})
require.NoError(t, err)

const initialBackendPrefix = "initial_prefix"
const additionalBackendPrefix = "additional_prefix"

service, err := NewServiceWrapper[*testResource153](memBackend,
"generic resource",
initialBackendPrefix,
marshalResource153,
unmarshalResource153)
require.NoError(t, err)

// Verify that the service's backend prefix matches the initial backend prefix.
require.Equal(t, initialBackendPrefix, service.service.backendPrefix)

// Verify that withPrefix appends the the additional prefix.
serviceWithPrefix := service.WithPrefix(additionalBackendPrefix)
require.Equal(t, "initial_prefix/additional_prefix", serviceWithPrefix.service.backendPrefix)
}
Loading
Loading