diff --git a/documentation/docs/reference/services/Notifications.md b/documentation/docs/reference/services/Notifications.md index 1cc46e11..62e9506d 100644 --- a/documentation/docs/reference/services/Notifications.md +++ b/documentation/docs/reference/services/Notifications.md @@ -194,6 +194,26 @@ Response format: } ``` +DELETE /notifications/device/ +--------------------------- + +Unregisters the specified device token from the current user. + +Request format: +``` +{ + "token": "example_token", + "platform": "android" +} +``` + +Response format: +``` +{ + "devices": [] +} +``` + GET /notifications/order/ID/ ---------------------------- diff --git a/gateway/services/notifications.go b/gateway/services/notifications.go index 710e803a..63e759bb 100644 --- a/gateway/services/notifications.go +++ b/gateway/services/notifications.go @@ -5,9 +5,10 @@ import ( "github.com/HackIllinois/api/gateway/middleware" "github.com/HackIllinois/api/gateway/models" + "net/http" + "github.com/arbor-dev/arbor" "github.com/justinas/alice" - "net/http" ) const NotificationsFormat string = "JSON" @@ -73,6 +74,12 @@ var NotificationsRoutes = arbor.RouteCollection{ "/notifications/device/", alice.New(middleware.IdentificationMiddleware, middleware.AuthMiddleware([]models.Role{models.UserRole})).ThenFunc(RegisterDeviceToUser).ServeHTTP, }, + arbor.Route{ + "UnregisterDeviceToUser", + "DELETE", + "/notifications/device/", + alice.New(middleware.IdentificationMiddleware, middleware.AuthMiddleware([]models.Role{models.UserRole})).ThenFunc(UnregisterDeviceToUser).ServeHTTP, + }, arbor.Route{ "GetNotificationOrder", "GET", @@ -121,6 +128,10 @@ func RegisterDeviceToUser(w http.ResponseWriter, r *http.Request) { arbor.POST(w, config.NOTIFICATIONS_SERVICE+r.URL.String(), NotificationsFormat, "", r) } +func UnregisterDeviceToUser(w http.ResponseWriter, r *http.Request) { + arbor.POST(w, config.NOTIFICATIONS_SERVICE+r.URL.String(), NotificationsFormat, "", r) +} + func GetNotificationOrder(w http.ResponseWriter, r *http.Request) { arbor.GET(w, config.NOTIFICATIONS_SERVICE+r.URL.String(), NotificationsFormat, "", r) } diff --git a/services/notifications/controller/controller.go b/services/notifications/controller/controller.go index 0fa72150..0a95937b 100644 --- a/services/notifications/controller/controller.go +++ b/services/notifications/controller/controller.go @@ -2,13 +2,14 @@ package controller import ( "encoding/json" + "net/http" + "time" + "github.com/HackIllinois/api/common/errors" "github.com/HackIllinois/api/common/utils" "github.com/HackIllinois/api/services/notifications/models" "github.com/HackIllinois/api/services/notifications/service" "github.com/gorilla/mux" - "net/http" - "time" ) func SetupController(route *mux.Route) { @@ -24,6 +25,7 @@ func SetupController(route *mux.Route) { router.HandleFunc("/topic/{id}/subscribe/", SubscribeToTopic).Methods("POST") router.HandleFunc("/topic/{id}/unsubscribe/", UnsubscribeToTopic).Methods("POST") router.HandleFunc("/device/", RegisterDeviceToUser).Methods("POST") + router.HandleFunc("/device/", UnregisterDeviceFromUser).Methods("DELETE") router.HandleFunc("/order/{id}/", GetNotificationOrder).Methods("GET") } @@ -258,6 +260,36 @@ func RegisterDeviceToUser(w http.ResponseWriter, r *http.Request) { json.NewEncoder(w).Encode(device_list) } +/* + Unregistered the specified device token from the user +*/ +func UnregisterDeviceFromUser(w http.ResponseWriter, r *http.Request) { + id := r.Header.Get("HackIllinois-Identity") + + var device_registration models.DeviceRegistration + json.NewDecoder(r.Body).Decode(&device_registration) + + err := service.UnregisterDeviceFromUser(device_registration.Token, device_registration.Platform, id) + + if err != nil { + errors.WriteError(w, r, errors.InternalError(err.Error(), "Failed to unregister device from user.")) + return + } + + devices, err := service.GetUserDevices(id) + + if err != nil { + errors.WriteError(w, r, errors.DatabaseError(err.Error(), "Failed to retrieve user's devices.")) + return + } + + device_list := models.DeviceList{ + Devices: devices, + } + + json.NewEncoder(w).Encode(device_list) +} + /* Returns the notification order with the specified id */ diff --git a/services/notifications/service/notifications_service.go b/services/notifications/service/notifications_service.go index b1ac48bd..58f5fc9e 100644 --- a/services/notifications/service/notifications_service.go +++ b/services/notifications/service/notifications_service.go @@ -3,13 +3,15 @@ package service import ( "encoding/json" "errors" + "strings" + "github.com/HackIllinois/api/common/database" + "github.com/HackIllinois/api/common/utils" "github.com/HackIllinois/api/services/notifications/config" "github.com/HackIllinois/api/services/notifications/models" "github.com/aws/aws-sdk-go/aws" "github.com/aws/aws-sdk-go/aws/session" "github.com/aws/aws-sdk-go/service/sns" - "strings" ) var SNS_MESSAGE_STRUCTURE string = "json" @@ -355,6 +357,70 @@ func RegisterDeviceToUser(token string, platform string, id string) error { return nil } +/* + Unregisters the device token with SNS and remove the arn from the associated user +*/ +func UnregisterDeviceFromUser(token string, platform string, id string) error { + var platform_arn string + + switch strings.ToLower(platform) { + case "android": + platform_arn = config.ANDROID_PLATFORM_ARN + case "ios": + platform_arn = config.IOS_PLATFORM_ARN + default: + return errors.New("Invalid platform") + } + + var device_arn string + + if config.IS_PRODUCTION { + response, err := client.CreatePlatformEndpoint( + &sns.CreatePlatformEndpointInput{ + CustomUserData: &id, + Token: &token, + PlatformApplicationArn: &platform_arn, + }, + ) + + if err != nil { + return err + } + + device_arn = *response.EndpointArn + + _, err = client.DeleteEndpoint( + &sns.DeleteEndpointInput{ + EndpointArn: &device_arn, + }, + ) + + if err != nil { + return err + } + } + + devices, err := GetUserDevices(id) + + if err != nil { + return err + } + + devices, err = utils.RemoveString(devices, device_arn) + + if err != nil { + return err + } + + err = SetUserDevices(id, devices) + + if err != nil { + return err + } + + return nil +} + /* Returns a list of userids to receive a notification to the specified topic */ diff --git a/services/notifications/tests/notifications_test.go b/services/notifications/tests/notifications_test.go index 7bf85caa..28336da6 100644 --- a/services/notifications/tests/notifications_test.go +++ b/services/notifications/tests/notifications_test.go @@ -1,14 +1,16 @@ package tests import ( + "errors" "fmt" + "os" + "reflect" + "testing" + "github.com/HackIllinois/api/common/database" "github.com/HackIllinois/api/services/notifications/config" "github.com/HackIllinois/api/services/notifications/models" "github.com/HackIllinois/api/services/notifications/service" - "os" - "reflect" - "testing" ) var db database.Database @@ -381,6 +383,53 @@ func TestRegisterDeviceToUser(t *testing.T) { CleanupTestDB(t) } +/* + Tests unregistering a device for a user +*/ +func TestUnregisterDeviceToUser(t *testing.T) { + SetupTestDB(t) + + // unregister known device + err := service.SetUserDevices("test_user", []string{"test_arn", "", "test_arn2"}) + if err != nil { + t.Fatal(err) + } + + err = service.UnregisterDeviceFromUser("test_token", "android", "test_user") + + if err != nil { + t.Fatal(err) + } + + devices, err := service.GetUserDevices("test_user") + + if err != nil { + t.Fatal(err) + } + + expected_devices := []string{"test_arn", "test_arn2"} + + if !reflect.DeepEqual(devices, expected_devices) { + t.Errorf("Wrong devices.\nExpected %v\ngot %v\n", expected_devices, devices) + } + + // unregister unknown device + err = service.UnregisterDeviceFromUser("test_token", "android", "test_user") + fmt.Printf("got error: %v\n", err) + + expected_error := errors.New("Value to remove not found") + + if err == nil { + t.Errorf("Wrong error.\nExpected %v\ngot %v\n", expected_error, nil) + } + + if err.Error() != expected_error.Error() { + t.Errorf("Wrong error.\nExpected %v\ngot %v\n", expected_error.Error(), err.Error()) + } + + CleanupTestDB(t) +} + /* Tests getting the list of userids to receive a notification */