Skip to content

Commit

Permalink
Merge pull request #9 from SwimResults/develop
Browse files Browse the repository at this point in the history
Develop
  • Loading branch information
konrad2002 authored Dec 5, 2024
2 parents d245711 + a31d0b8 commit c199bc8
Show file tree
Hide file tree
Showing 17 changed files with 644 additions and 64 deletions.
69 changes: 69 additions & 0 deletions client/notification_client.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
package client

import (
"encoding/json"
"fmt"
"github.com/swimresults/service-core/client"
"github.com/swimresults/user-service/dto"
"net/http"
)

type NotificationClient struct {
apiUrl string
}

func NewNotificationClient(url string) *NotificationClient {
return &NotificationClient{apiUrl: url}
}

func (c *NotificationClient) SendNotification(key string, meeting string, title string, subtitle string, message string) (*dto.NotificationResponseDto, error) {
request := dto.NotificationRequestDto{
Title: title,
Subtitle: subtitle,
Message: message,
}

header := http.Header{}
header.Set("X-SWIMRESULTS-SERVICE", key)

res, err := client.Post(c.apiUrl, "notification/import", request, &header)
if err != nil {
return nil, err
}
defer res.Body.Close()

responseDto := &dto.NotificationResponseDto{}
err = json.NewDecoder(res.Body).Decode(responseDto)
if err != nil {
return nil, err
}

if res.StatusCode != http.StatusOK {
return nil, fmt.Errorf("notification request returned: %d", res.StatusCode)
}
return responseDto, nil
}

func (c *NotificationClient) SendMeetingBroadcastNotification(key string, meeting string, body interface{}) (*dto.BroadcastResponseDto, error) {
fmt.Printf("sending meeting broadcast request to: '%s'\n", "notification/broadcast/meeting/"+meeting)

header := http.Header{}
header.Set("X-SWIMRESULTS-SERVICE", key)

res, err := client.Post(c.apiUrl, "notification/broadcast/meeting/"+meeting, body, &header)
if err != nil {
return nil, err
}
defer res.Body.Close()

responseDto := &dto.BroadcastResponseDto{}
err = json.NewDecoder(res.Body).Decode(responseDto)
if err != nil {
return nil, err
}

if res.StatusCode != http.StatusOK {
return nil, fmt.Errorf("meeting broadcast request returned: %d", res.StatusCode)
}
return responseDto, nil
}
18 changes: 18 additions & 0 deletions client/notification_client_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package client

//
//import (
// "fmt"
// "testing"
//)
//
//func TestNotificationClient_SendMeetingBroadcastNotification(t *testing.T) {
// client := NewNotificationClient("http://localhost:8090/")
//
// r, e := client.SendMeetingBroadcastNotification("12345678", "IESC", `"test": {}`)
// if e != nil {
// fmt.Printf(e.Error())
// }
// fmt.Println(r)
// //fmt.Printf("id: %s, number: %d, start: %s", r.Identifier.String(), r.Number, r.StartEstimation.Format("15:04"))
//}
98 changes: 98 additions & 0 deletions controller/controller.go
Original file line number Diff line number Diff line change
@@ -1,14 +1,20 @@
package controller

import (
"errors"
"fmt"
"github.com/dgrijalva/jwt-go"
"github.com/gin-gonic/gin"
"github.com/google/uuid"
"github.com/swimresults/user-service/model"
"github.com/swimresults/user-service/service"
"net/http"
"os"
"strings"
)

var router = gin.Default()
var serviceKey string

func Run() {

Expand All @@ -19,6 +25,13 @@ func Run() {
return
}

serviceKey = os.Getenv("SR_SERVICE_KEY")

if serviceKey == "" {
fmt.Println("no security for inter-service communication given! Please set SR_SERVICE_KEY.")
return
}

userController()
widgetController()
dashboardController()
Expand All @@ -43,3 +56,88 @@ func actuator(c *gin.Context) {
}
c.String(http.StatusOK, state)
}

func checkServiceKey(c *gin.Context) error {
println("checking service key...")
received := c.Request.Header["X-Swimresults-Service"]
fmt.Printf("received: '%s', expected: '%s'\n", received, serviceKey)
if len(received) <= 0 {
return errors.New("no service authorization key in header")
}
if received[0] == serviceKey {
return nil
}

return errors.New("invalid service authorization key in header")
}

func checkAuthHeaderToken(c *gin.Context) error {
claims, err1 := getClaimsFromAuthHeader(c)

if err1 != nil {
return err1
}

if !claims.IsRoot() {
return errors.New("insufficient permissions")
}

return nil
}

func getClaimsFromAuthHeader(c *gin.Context) (*model.TokenClaims, error) {
if len(c.Request.Header["Authorization"]) == 0 {
err1 := errors.New("no authorization in header")
c.IndentedJSON(http.StatusUnauthorized, err1.Error())
return nil, err1
}

tokenString := strings.Split(c.Request.Header["Authorization"][0], " ")[1]

token, err1 := jwt.Parse(tokenString, nil)
if token == nil {
return nil, err1
}
claims, _ := token.Claims.(jwt.MapClaims)
sub := fmt.Sprintf("%s", claims["sub"])

id, err2 := uuid.Parse(sub)
if err2 != nil {
return nil, err2
}

var tokenClaims model.TokenClaims

tokenClaims.Sub = id
tokenClaims.Scopes = strings.Split(fmt.Sprintf("%s", claims["scope"]), " ")

return &tokenClaims, nil
}

func checkIfRoot(c *gin.Context) error {
keyError := checkServiceKey(c)
if keyError == nil {
return nil
}

tokenError := checkAuthHeaderToken(c)

if tokenError == nil {
return nil
} else {
fmt.Printf("both auth checks for root failed: \n%s\n%s\n", keyError, tokenError)
return tokenError
}
}

// failIfNotRoot returns true if the requester is not root or a service
func failIfNotRoot(c *gin.Context) bool {
err := checkIfRoot(c)

if err == nil {
return false
} else {
c.IndentedJSON(http.StatusUnauthorized, err.Error())
return true
}
}
139 changes: 138 additions & 1 deletion controller/notification_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,20 @@ import (
"github.com/gin-gonic/gin"
"github.com/swimresults/user-service/dto"
"github.com/swimresults/user-service/service"
"go.mongodb.org/mongo-driver/bson/primitive"
"io"
"net/http"
)

func notificationController() {

router.POST("/notification/test/:device", sendTestNotification)
router.POST("/notification/:device", sendNotification)
router.POST("/notification/meet/:meeting", sendNotificationForMeeting)
router.POST("/notification/meet/:meeting/athlete/:athlete", sendNotificationForMeetingAndAthlete)

router.POST("/notification/broadcast/:channel", sendBroadcast)
router.POST("/notification/broadcast/meeting/:meeting", sendMeetingBroadcast)

router.OPTIONS("/notification/test/:device", okay)
router.OPTIONS("/notification/:device", okay)
Expand Down Expand Up @@ -47,7 +54,7 @@ func sendNotification(c *gin.Context) {
return
}

apnsId, body, status, err := service.SendPushNotification(device, request.Title, request.Subtitle, request.Message)
apnsId, body, status, err := service.SendPushNotification(device, request.Title, request.Subtitle, request.Message, request.InterruptionLevel)
if err != nil {
c.IndentedJSON(http.StatusInternalServerError, gin.H{"message": err.Error()})
return
Expand All @@ -58,3 +65,133 @@ func sendNotification(c *gin.Context) {
Body: body,
})
}

func sendNotificationForMeeting(c *gin.Context) {

if failIfNotRoot(c) {
return
}

meeting := c.Param("meeting")

if meeting == "" {
c.IndentedJSON(http.StatusNotFound, gin.H{"message": "no meeting given"})
return
}

var request dto.MeetingNotificationRequestDto
if err := c.BindJSON(&request); err != nil {
c.IndentedJSON(http.StatusInternalServerError, gin.H{"message": err.Error()})
return
}

users, notificationUsers, success, err := service.SendPushNotificationForMeeting(meeting, request)
if err != nil {
c.IndentedJSON(http.StatusInternalServerError, gin.H{"message": err.Error()})
return
}

c.IndentedJSON(http.StatusOK, dto.NotificationsResponseDto{
UserCount: users,
NotificationUserCount: notificationUsers,
SuccessCount: success,
})
}

func sendNotificationForMeetingAndAthlete(c *gin.Context) {

if failIfNotRoot(c) {
return
}

meeting := c.Param("meeting")

if meeting == "" {
c.IndentedJSON(http.StatusNotFound, gin.H{"message": "no meeting given"})
return
}

athleteId, convErr := primitive.ObjectIDFromHex(c.Param("athlete"))
if convErr != nil {
c.IndentedJSON(http.StatusBadRequest, gin.H{"message": "given athlete was not of type ObjectID"})
return
}

var request dto.MeetingNotificationRequestDto
if err := c.BindJSON(&request); err != nil {
c.IndentedJSON(http.StatusInternalServerError, gin.H{"message": err.Error()})
return
}

users, notificationUsers, success, err := service.SendPushNotificationForMeetingAndAthletes(meeting, []primitive.ObjectID{athleteId}, request)
if err != nil {
c.IndentedJSON(http.StatusInternalServerError, gin.H{"message": err.Error()})
return
}

c.IndentedJSON(http.StatusOK, dto.NotificationsResponseDto{
UserCount: users,
NotificationUserCount: notificationUsers,
SuccessCount: success,
})
}

func sendBroadcast(c *gin.Context) {

if failIfNotRoot(c) {
return
}

channel := c.Param("channel")

content, err := io.ReadAll(c.Request.Body)
if err != nil {
c.IndentedJSON(http.StatusInternalServerError, gin.H{"message": err.Error()})
return
}

print("channel: " + channel)
print("content: " + string(content))

apnsRequestId, apnsUniqueId, body, status, err := service.SendPushBroadcast(channel, string(content))
if err != nil {
c.IndentedJSON(http.StatusInternalServerError, gin.H{"message": err.Error()})
return
}

c.IndentedJSON(status, dto.BroadcastResponseDto{
ApnsRequestId: apnsRequestId,
ApnsUniqueId: apnsUniqueId,
Body: body,
})
}

func sendMeetingBroadcast(c *gin.Context) {

if failIfNotRoot(c) {
return
}

meeting := c.Param("meeting")

content, err := io.ReadAll(c.Request.Body)
if err != nil {
c.IndentedJSON(http.StatusInternalServerError, gin.H{"message": err.Error()})
return
}

print("meeting: " + meeting)
print("content: " + string(content))

apnsRequestId, apnsUniqueId, body, status, err := service.SendPushMeetingBroadcast(meeting, string(content))
if err != nil {
c.IndentedJSON(http.StatusInternalServerError, gin.H{"message": err.Error()})
return
}

c.IndentedJSON(status, dto.BroadcastResponseDto{
ApnsRequestId: apnsRequestId,
ApnsUniqueId: apnsUniqueId,
Body: body,
})
}
Loading

0 comments on commit c199bc8

Please sign in to comment.