Skip to content

Commit dfa9810

Browse files
committed
feat: add pushnotifications plugin
Signed-off-by: Ales Verbic <[email protected]>
1 parent fe09318 commit dfa9810

File tree

13 files changed

+722
-77
lines changed

13 files changed

+722
-77
lines changed

api/api.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ func WithPort(port string) APIOption {
4949
var apiInstance *APIv1
5050
var once sync.Once
5151

52-
func NewAPI(debug bool, options ...APIOption) *APIv1 {
52+
func New(debug bool, options ...APIOption) *APIv1 {
5353
once.Do(func() {
5454
apiInstance = &APIv1{
5555
engine: ConfigureRouter(debug),

api/api_test.go

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -12,15 +12,15 @@ import (
1212

1313
func TestRouteRegistration(t *testing.T) {
1414
// Initialize the API and set it to debug mode for testing
15-
apiInstance := api.NewAPI(true)
15+
apiInstance := api.New(true)
1616

1717
// Check if Fcm implements APIRouteRegistrar and register its routes
1818
// TODO: update this with actual plugin
19-
fcmPlugin := &push.Fcm{}
20-
if registrar, ok := interface{}(fcmPlugin).(api.APIRouteRegistrar); ok {
19+
pushPlugin := &push.PushOutput{}
20+
if registrar, ok := interface{}(pushPlugin).(api.APIRouteRegistrar); ok {
2121
registrar.RegisterRoutes()
2222
} else {
23-
t.Fatal("push.Fcm does NOT implement APIRouteRegistrar")
23+
t.Fatal("pushPlugin does NOT implement APIRouteRegistrar")
2424
}
2525

2626
// Create a test request to one of the registered routes

cmd/snek/main.go

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -105,7 +105,7 @@ func main() {
105105
}
106106

107107
// Create API instance with debug disabled
108-
apiInstance := api.NewAPI(false,
108+
apiInstance := api.New(true,
109109
api.WithGroup("/v1"),
110110
api.WithPort("8080"))
111111

@@ -130,6 +130,10 @@ func main() {
130130
if output == nil {
131131
logger.Fatalf("unknown output: %s", cfg.Output)
132132
}
133+
// Check if output plugin implements APIRouteRegistrar
134+
if registrar, ok := interface{}(output).(api.APIRouteRegistrar); ok {
135+
registrar.RegisterRoutes()
136+
}
133137
pipe.AddOutput(output)
134138

135139
// Start API after plugins are configured

fcm/message.go

Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
package fcm
2+
3+
import (
4+
"bytes"
5+
"encoding/json"
6+
"errors"
7+
"fmt"
8+
"io"
9+
"net/http"
10+
11+
"github.com/blinklabs-io/snek/internal/logging"
12+
)
13+
14+
type Message struct {
15+
MessageContent `json:"message"`
16+
}
17+
18+
type MessageContent struct {
19+
Token string `json:"token"`
20+
Notification *NotificationContent `json:"notification,omitempty"`
21+
Data map[string]interface{} `json:"data,omitempty"`
22+
}
23+
24+
type NotificationContent struct {
25+
Title string `json:"title"`
26+
Body string `json:"body"`
27+
}
28+
29+
type MessageOption func(*MessageContent)
30+
31+
func WithData(data map[string]interface{}) MessageOption {
32+
return func(m *MessageContent) {
33+
m.Data = data
34+
}
35+
}
36+
37+
func WithNotification(title string, body string) MessageOption {
38+
return func(m *MessageContent) {
39+
m.Notification = &NotificationContent{
40+
Title: title,
41+
Body: body,
42+
}
43+
}
44+
}
45+
46+
func NewMessage(token string, opts ...MessageOption) *Message {
47+
if token == "" {
48+
logging.GetLogger().Fatalf("Token is mandatory for FCM message")
49+
}
50+
51+
msg := &Message{
52+
MessageContent: MessageContent{
53+
Token: token,
54+
},
55+
}
56+
for _, opt := range opts {
57+
opt(&msg.MessageContent)
58+
}
59+
return msg
60+
}
61+
62+
func Send(accessToken string, projectId string, msg *Message) error {
63+
64+
fcmEndpoint := fmt.Sprintf("https://fcm.googleapis.com/v1/projects/%s/messages:send", projectId)
65+
66+
// Convert the message to JSON
67+
payload, err := json.Marshal(msg)
68+
if err != nil {
69+
return err
70+
}
71+
72+
fmt.Println(string(payload))
73+
74+
// Create a new HTTP request
75+
req, err := http.NewRequest("POST", fcmEndpoint, bytes.NewBuffer(payload))
76+
if err != nil {
77+
return err
78+
}
79+
80+
// Set headers
81+
req.Header.Set("Authorization", "Bearer "+accessToken)
82+
req.Header.Set("Content-Type", "application/json")
83+
84+
// Execute the request
85+
client := &http.Client{}
86+
resp, err := client.Do(req)
87+
if err != nil {
88+
return err
89+
}
90+
defer resp.Body.Close()
91+
92+
// Check for errors in the response
93+
if resp.StatusCode != http.StatusOK {
94+
body, _ := io.ReadAll(resp.Body)
95+
return errors.New(string(body))
96+
}
97+
98+
return nil
99+
}

go.mod

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,13 +9,16 @@ require (
99
github.com/kelseyhightower/envconfig v1.4.0
1010
github.com/stretchr/testify v1.8.4
1111
go.uber.org/zap v1.26.0
12+
golang.org/x/oauth2 v0.11.0
1213
gopkg.in/yaml.v2 v2.4.0
1314
)
1415

1516
// XXX: uncomment when testing local changes to gouroboros
1617
// replace github.com/blinklabs-io/gouroboros v0.52.0 => ../gouroboros
1718

1819
require (
20+
cloud.google.com/go/compute v1.20.1 // indirect
21+
cloud.google.com/go/compute/metadata v0.2.3 // indirect
1922
github.com/bytedance/sonic v1.10.1 // indirect
2023
github.com/chenzhuoyu/base64x v0.0.0-20230717121745-296ad89f973d // indirect
2124
github.com/chenzhuoyu/iasm v0.9.0 // indirect
@@ -29,6 +32,7 @@ require (
2932
github.com/go-toast/toast v0.0.0-20190211030409-01e6764cf0a4 // indirect
3033
github.com/goccy/go-json v0.10.2 // indirect
3134
github.com/godbus/dbus/v5 v5.1.0 // indirect
35+
github.com/golang/protobuf v1.5.3 // indirect
3236
github.com/jinzhu/copier v0.4.0 // indirect
3337
github.com/json-iterator/go v1.1.12 // indirect
3438
github.com/klauspost/cpuid/v2 v2.2.5 // indirect
@@ -50,6 +54,7 @@ require (
5054
golang.org/x/net v0.17.0 // indirect
5155
golang.org/x/sys v0.13.0 // indirect
5256
golang.org/x/text v0.13.0 // indirect
57+
google.golang.org/appengine v1.6.7 // indirect
5358
google.golang.org/protobuf v1.31.0 // indirect
5459
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect
5560
gopkg.in/yaml.v3 v3.0.1 // indirect

go.sum

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
1+
cloud.google.com/go/compute v1.20.1 h1:6aKEtlUiwEpJzM001l0yFkpXmUVXaN8W+fbkb2AZNbg=
2+
cloud.google.com/go/compute v1.20.1/go.mod h1:4tCnrn48xsqlwSAiLf1HXMQk8CONslYbdiEZc9FEIbM=
3+
cloud.google.com/go/compute/metadata v0.2.3 h1:mg4jlk7mCAj6xXp9UJ4fjI9VUI5rubuGBW5aJ7UnBMY=
4+
cloud.google.com/go/compute/metadata v0.2.3/go.mod h1:VAV5nSsACxMJvgaAuX6Pk2AawlZn8kiOGuCv6gTkwuA=
15
github.com/blinklabs-io/gouroboros v0.54.0 h1:ZRp+L7Xb2wRn3N02a7EQ8jpBfqL6FKjnpauJdCiHEqE=
26
github.com/blinklabs-io/gouroboros v0.54.0/go.mod h1:ID2Lq1XtYrBvmk/y+yQiX45sZiV8n+urOmT0s46d2+U=
37
github.com/bytedance/sonic v1.5.0/go.mod h1:ED5hyg4y6t3/9Ku1R6dU/4KyJ48DZ4jPhfY1O2AihPM=
@@ -37,9 +41,12 @@ github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU=
3741
github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
3842
github.com/godbus/dbus/v5 v5.1.0 h1:4KLkAxT3aOY8Li4FRJe/KvhoNFFxo0m6fNuFUO8QJUk=
3943
github.com/godbus/dbus/v5 v5.1.0/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
44+
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
4045
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
41-
github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU=
46+
github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg=
47+
github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
4248
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
49+
github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
4350
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
4451
github.com/jinzhu/copier v0.4.0 h1:w3ciUoD19shMCRargcpm0cm91ytaBhDvuRpz1ODO/U8=
4552
github.com/jinzhu/copier v0.4.0/go.mod h1:DfbEm0FYsaqBcKcFuvmOZb218JkPGtvSHsKg8S8hyyg=
@@ -103,19 +110,29 @@ go.uber.org/zap v1.26.0/go.mod h1:dtElttAiwGvoJ/vj4IwHBS/gXsEu/pZ50mUIRWuG0so=
103110
golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8=
104111
golang.org/x/arch v0.5.0 h1:jpGode6huXQxcskEIpOCvrU+tzo81b6+oFLUYXWtH/Y=
105112
golang.org/x/arch v0.5.0/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8=
113+
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
106114
golang.org/x/crypto v0.14.0 h1:wBqGXzWJW6m1XrIKlAH0Hs1JJ7+9KBwnIO8v66Q9cHc=
107115
golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4=
116+
golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
108117
golang.org/x/net v0.17.0 h1:pVaXccu2ozPjCXewfr1S7xza/zcXTity9cCdXQYSjIM=
109118
golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE=
119+
golang.org/x/oauth2 v0.11.0 h1:vPL4xzxBM4niKCW6g9whtaWVXTJf1U5e4aZxxFx/gbU=
120+
golang.org/x/oauth2 v0.11.0/go.mod h1:LdF7O/8bLR/qWK9DrpXmbHLTouvRHK0SgJl0GmDBchk=
121+
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
110122
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
111123
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
112124
golang.org/x/sys v0.13.0 h1:Af8nKPmuFypiUBjVoU9V20FiaFXOcuZI21p0ycVYYGE=
113125
golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
126+
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
127+
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
114128
golang.org/x/text v0.13.0 h1:ablQoSUd0tRdKxZewP80B+BaqeKJuVhuRxj/dkrun3k=
115129
golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
116-
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4=
130+
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
117131
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
132+
google.golang.org/appengine v1.6.7 h1:FZR1q0exgwxzPzp/aF+VccGrSfxfPpkBqjIIEq3ru6c=
133+
google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
118134
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
135+
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
119136
google.golang.org/protobuf v1.31.0 h1:g0LDEJHgrBl9N9r17Ru3sqWhkIx2NB67okBHPwC7hs8=
120137
google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
121138
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=

output/push/api_routes.go

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
// Copyright 2023 Blink Labs, LLC.
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
package push
16+
17+
import (
18+
"github.com/blinklabs-io/snek/api"
19+
)
20+
21+
var routesRegistered = false
22+
23+
func (p *PushOutput) RegisterRoutes() {
24+
25+
if routesRegistered {
26+
return
27+
}
28+
29+
apiInstance := api.GetInstance()
30+
31+
apiInstance.AddRoute("POST", "/fcm", storeFCMToken)
32+
apiInstance.AddRoute("POST", "/fcm/", storeFCMToken)
33+
34+
apiInstance.AddRoute("GET", "/fcm/:token", readFCMToken)
35+
apiInstance.AddRoute("GET", "/fcm/:token/", readFCMToken)
36+
37+
apiInstance.AddRoute("DELETE", "/fcm/:token", deleteFCMToken)
38+
apiInstance.AddRoute("DELETE", "/fcm/:token/", deleteFCMToken)
39+
40+
routesRegistered = true
41+
}

0 commit comments

Comments
 (0)