feat(server): added API to manipulate notification profiles in the UI (#4171)

This commit is contained in:
Jarek Kowalski
2024-10-14 10:16:08 -07:00
committed by GitHub
parent eb2ea5dddd
commit 58bcb29d1a
3 changed files with 154 additions and 0 deletions

View File

@@ -0,0 +1,62 @@
package server
import (
"context"
"encoding/json"
"github.com/kopia/kopia/internal/serverapi"
"github.com/kopia/kopia/notification/notifyprofile"
"github.com/kopia/kopia/repo"
)
func handleNotificationProfileCreate(ctx context.Context, rc requestContext) (any, *apiError) {
var cfg notifyprofile.Config
if err := json.Unmarshal(rc.body, &cfg); err != nil {
return nil, requestError(serverapi.ErrorMalformedRequest, "malformed request body: "+string(rc.body))
}
if err := repo.WriteSession(ctx, rc.rep, repo.WriteSessionOptions{
Purpose: "NotificationProfileCreate",
}, func(ctx context.Context, w repo.RepositoryWriter) error {
return notifyprofile.SaveProfile(ctx, w, cfg)
}); err != nil {
return nil, internalServerError(err)
}
return &serverapi.Empty{}, nil
}
func handleNotificationProfileGet(ctx context.Context, rc requestContext) (any, *apiError) {
cfg, ok, err := notifyprofile.GetProfile(ctx, rc.rep, rc.muxVar("profileName"))
if err != nil {
return nil, internalServerError(err)
}
if !ok {
return nil, notFoundError("profile not found")
}
return cfg, nil
}
func handleNotificationProfileDelete(ctx context.Context, rc requestContext) (any, *apiError) {
if err := repo.WriteSession(ctx, rc.rep, repo.WriteSessionOptions{
Purpose: "NotificationProfileDelete",
}, func(ctx context.Context, w repo.RepositoryWriter) error {
return notifyprofile.DeleteProfile(ctx, w, rc.muxVar("profileName"))
}); err != nil {
return nil, internalServerError(err)
}
return &serverapi.Empty{}, nil
}
func handleNotificationProfileList(ctx context.Context, rc requestContext) (any, *apiError) {
profiles, err := notifyprofile.ListProfiles(ctx, rc.rep)
if err != nil {
return nil, internalServerError(err)
}
return profiles, nil
}

View File

@@ -0,0 +1,87 @@
package server_test
import (
"testing"
"github.com/stretchr/testify/require"
"github.com/kopia/kopia/internal/apiclient"
"github.com/kopia/kopia/internal/repotesting"
"github.com/kopia/kopia/internal/serverapi"
"github.com/kopia/kopia/internal/servertesting"
"github.com/kopia/kopia/notification/notifyprofile"
"github.com/kopia/kopia/notification/sender"
"github.com/kopia/kopia/notification/sender/testsender"
)
func TestNotificationProfile(t *testing.T) {
ctx, env := repotesting.NewEnvironment(t, repotesting.FormatNotImportant)
srvInfo := servertesting.StartServer(t, env, false)
cli, err := apiclient.NewKopiaAPIClient(apiclient.Options{
BaseURL: srvInfo.BaseURL,
TrustedServerCertificateFingerprint: srvInfo.TrustedServerCertificateFingerprint,
Username: servertesting.TestUIUsername,
Password: servertesting.TestUIPassword,
})
require.NoError(t, err)
require.NoError(t, cli.FetchCSRFTokenForTesting(ctx))
var profiles []notifyprofile.Config
require.NoError(t, cli.Get(ctx, "notificationProfiles", nil, &profiles))
require.Empty(t, profiles)
// define new profile
require.NoError(t, cli.Post(ctx, "notificationProfiles", &notifyprofile.Config{
ProfileName: "profile1",
MethodConfig: sender.MethodConfig{
Type: "testsender",
Config: testsender.Options{
Format: "txt",
},
},
MinSeverity: 3,
}, &serverapi.Empty{}))
// define invalid profile
require.ErrorContains(t, cli.Post(ctx, "notificationProfiles", &notifyprofile.Config{
ProfileName: "profile2",
MethodConfig: sender.MethodConfig{
Type: "no-such-type",
Config: testsender.Options{
Format: "txt",
},
},
MinSeverity: 3,
}, &serverapi.Empty{}), "malformed request body")
var cfg notifyprofile.Config
// get profile and verify
require.NoError(t, cli.Get(ctx, "notificationProfiles/profile1", nil, &cfg))
require.Equal(t, "profile1", cfg.ProfileName)
require.Equal(t, sender.Method("testsender"), cfg.MethodConfig.Type)
opt, ok := cfg.MethodConfig.Config.(map[string]any)
require.True(t, ok)
require.Equal(t, "txt", opt["format"])
// get non-existent profile
require.ErrorContains(t, cli.Get(ctx, "notificationProfiles/profile2", nil, &cfg), "profile not found")
// list profiles
require.NoError(t, cli.Get(ctx, "notificationProfiles", nil, &profiles))
require.Len(t, profiles, 1)
require.Equal(t, "profile1", profiles[0].ProfileName)
// delete the profile, ensure idempotent
require.NoError(t, cli.Delete(ctx, "notificationProfiles/profile1", nil, nil, &serverapi.Empty{}))
require.NoError(t, cli.Delete(ctx, "notificationProfiles/profile1", nil, nil, &serverapi.Empty{}))
// verify it's gone
require.NoError(t, cli.Get(ctx, "notificationProfiles", nil, &profiles))
require.Empty(t, profiles)
}

View File

@@ -158,6 +158,11 @@ func (s *Server) SetupHTMLUIAPIHandlers(m *mux.Router) {
m.HandleFunc("/api/v1/tasks/{taskID}", s.handleUIPossiblyNotConnected(handleTaskInfo)).Methods(http.MethodGet)
m.HandleFunc("/api/v1/tasks/{taskID}/logs", s.handleUIPossiblyNotConnected(handleTaskLogs)).Methods(http.MethodGet)
m.HandleFunc("/api/v1/tasks/{taskID}/cancel", s.handleUIPossiblyNotConnected(handleTaskCancel)).Methods(http.MethodPost)
m.HandleFunc("/api/v1/notificationProfiles", s.handleUI(handleNotificationProfileCreate)).Methods(http.MethodPost)
m.HandleFunc("/api/v1/notificationProfiles/{profileName}", s.handleUI(handleNotificationProfileDelete)).Methods(http.MethodDelete)
m.HandleFunc("/api/v1/notificationProfiles/{profileName}", s.handleUI(handleNotificationProfileGet)).Methods(http.MethodGet)
m.HandleFunc("/api/v1/notificationProfiles", s.handleUI(handleNotificationProfileList)).Methods(http.MethodGet)
}
// SetupControlAPIHandlers registers control API handlers.