Skip to content

Commit

Permalink
feat: implement a new Push Notification service that sends push notif…
Browse files Browse the repository at this point in the history
… on PostReply transactions

Signed-off-by: D4ryl00 <[email protected]>
  • Loading branch information
D4ryl00 committed Jun 25, 2024
1 parent b8684a5 commit 11d53e3
Show file tree
Hide file tree
Showing 24 changed files with 2,777 additions and 0 deletions.
4 changes: 4 additions & 0 deletions tools/notification-service/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
build

# default database path
data.db
1 change: 1 addition & 0 deletions tools/notification-service/.tool-versions
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
buf 1.30.1
14 changes: 14 additions & 0 deletions tools/notification-service/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
FROM golang:1.21-alpine AS builder

COPY . /app

WORKDIR /app

RUN go build -o notification .


FROM alpine

COPY --from=builder /app/notification /usr/local/bin/notification

ENTRYPOINT [ "/usr/local/bin/notification" ]
28 changes: 28 additions & 0 deletions tools/notification-service/Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
src := $(wildcard ./*.go) go.mod go.sum

#### API ####

api/gen: api/notificationservice.proto
buf generate api
touch api/gen

api.build: api/gen
.PHONY: api.build

api.push: api.build
buf push api
.PHONY: api.push

#### BUILD ####

build/notification: $(src)
mkdir -p build
go build -o build/notification .

build: build/notification
.PHONY: build

#### CLEAN ####

clean:
rm -rf build
112 changes: 112 additions & 0 deletions tools/notification-service/api.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
package main

import (
"bytes"
"context"
"encoding/gob"
"time"

"connectrpc.com/connect"
"go.uber.org/zap"

api_gen "github.com/gnolang/dsocial/tools/notification-service/api/gen/go"
"github.com/pkg/errors"
)

func (s *notificationService) RegisterDevice(ctx context.Context, req *connect.Request[api_gen.RegisterDeviceRequest]) (*connect.Response[api_gen.RegisterDeviceResponse], error) {
s.logger.Debug("RegisterDevice called", zap.String("address", req.Msg.Address), zap.String("token", req.Msg.Token))

if req.Msg.Address == "" || req.Msg.Token == "" {
s.logger.Error("RegisterDevice error: missing input", zap.String("address", req.Msg.Address), zap.String("token", req.Msg.Token))
return nil, api_gen.ErrCode_ErrMissingInput
}

if err := s.registerDevice(req.Msg.Address, req.Msg.Token); err != nil {
s.logger.Error("RegisterDevice error: failed to register device", zap.Error(err))
return nil, api_gen.ErrCode_ErrInternal
}

s.logger.Debug("RegisterDevice returned ok")

return connect.NewResponse(&api_gen.RegisterDeviceResponse{}), nil
}

func (s *notificationService) registerDevice(address string, token string) error {
var tokens []string
var err error

if tokens, err = s.getPushTokens(address); err != nil {
return errors.Wrap(err, "registerDevice error: getPushToken")
}

for _, t := range tokens {
if t == token {
s.logger.Info("registerDevice: token already exists", zap.String("address", address), zap.String("token", token))
return nil
}
}

tokens = append(tokens, token)

var buffer bytes.Buffer
enc := gob.NewEncoder(&buffer)
if err := enc.Encode(tokens); err != nil {
return errors.Wrap(err, "registerDevice error: encode")
}

if err := s.db.Set([]byte(address), buffer.Bytes()); err != nil {
return errors.Wrap(err, "registerDevice error: DB.Set")
}

return nil
}

// getPushTokens returns the push tokens slice for the given address
// If the address has no tokens, it returns an empty string slice
func (s *notificationService) getPushTokens(address string) ([]string, error) {
rawTokens, err := s.db.Get([]byte(address))
if err != nil {
return nil, errors.Wrap(err, "getPushTokens error: DB.Get")
}

// handle empty tokens
if len(rawTokens) == 0 {
return []string{}, nil
}

buffer := bytes.NewBuffer(rawTokens)
var tokens []string

dec := gob.NewDecoder(buffer)
if err := dec.Decode(&tokens); err != nil {
return nil, errors.Wrap(err, "getPushTokens error: decode")
}

return tokens, nil
}

// Hello is for debug purposes
func (s *notificationService) Hello(ctx context.Context, req *connect.Request[api_gen.HelloRequest]) (*connect.Response[api_gen.HelloResponse], error) {
s.logger.Debug("Hello called")
defer s.logger.Debug("Hello returned ok")
return connect.NewResponse(&api_gen.HelloResponse{
Greeting: "Hello " + req.Msg.Name,
}), nil
}

// HelloStream is for debug purposes
func (s *notificationService) HelloStream(ctx context.Context, req *connect.Request[api_gen.HelloStreamRequest], stream *connect.ServerStream[api_gen.HelloStreamResponse]) error {
s.logger.Debug("HelloStream called")
for i := 0; i < 4; i++ {
if err := stream.Send(&api_gen.HelloStreamResponse{
Greeting: "Hello " + req.Msg.Name,
}); err != nil {
s.logger.Error("HelloStream returned error", zap.Error(err))
return err
}
time.Sleep(2 * time.Second)
}

s.logger.Debug("HelloStream returned ok")
return nil
}
8 changes: 8 additions & 0 deletions tools/notification-service/api/buf.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
version: v1
name: buf.build/gnolang/dsocial-notification
breaking:
use:
- FILE
lint:
use:
- DEFAULT
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
// @generated by protoc-gen-connect-es v1.4.0
// @generated from file notificationservice.proto (package land.gno.dsocial.notificationservice.v1, syntax proto3)
/* eslint-disable */
// @ts-nocheck

import { HelloRequest, HelloResponse, HelloStreamRequest, HelloStreamResponse, RegisterDeviceRequest, RegisterDeviceResponse } from "./notificationservice_pb.js";
import { MethodKind } from "@bufbuild/protobuf";

/**
* NotificationService is the service to manage Push Notifications in your app.
*
* @generated from service land.gno.dsocial.notificationservice.v1.NotificationService
*/
export declare const NotificationService: {
readonly typeName: "land.gno.dsocial.notificationservice.v1.NotificationService",
readonly methods: {
/**
* @generated from rpc land.gno.dsocial.notificationservice.v1.NotificationService.RegisterDevice
*/
readonly registerDevice: {
readonly name: "RegisterDevice",
readonly I: typeof RegisterDeviceRequest,
readonly O: typeof RegisterDeviceResponse,
readonly kind: MethodKind.Unary,
},
/**
* Hello is for debug purposes
*
* @generated from rpc land.gno.dsocial.notificationservice.v1.NotificationService.Hello
*/
readonly hello: {
readonly name: "Hello",
readonly I: typeof HelloRequest,
readonly O: typeof HelloResponse,
readonly kind: MethodKind.Unary,
},
/**
* HelloStream is for debug purposes
*
* @generated from rpc land.gno.dsocial.notificationservice.v1.NotificationService.HelloStream
*/
readonly helloStream: {
readonly name: "HelloStream",
readonly I: typeof HelloStreamRequest,
readonly O: typeof HelloStreamResponse,
readonly kind: MethodKind.ServerStreaming,
},
}
};

Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
// @generated by protoc-gen-connect-es v1.4.0
// @generated from file notificationservice.proto (package land.gno.dsocial.notificationservice.v1, syntax proto3)
/* eslint-disable */
// @ts-nocheck

import { HelloRequest, HelloResponse, HelloStreamRequest, HelloStreamResponse, RegisterDeviceRequest, RegisterDeviceResponse } from "./notificationservice_pb.js";
import { MethodKind } from "@bufbuild/protobuf";

/**
* NotificationService is the service to manage Push Notifications in your app.
*
* @generated from service land.gno.dsocial.notificationservice.v1.NotificationService
*/
export const NotificationService = {
typeName: "land.gno.dsocial.notificationservice.v1.NotificationService",
methods: {
/**
* @generated from rpc land.gno.dsocial.notificationservice.v1.NotificationService.RegisterDevice
*/
registerDevice: {
name: "RegisterDevice",
I: RegisterDeviceRequest,
O: RegisterDeviceResponse,
kind: MethodKind.Unary,
},
/**
* Hello is for debug purposes
*
* @generated from rpc land.gno.dsocial.notificationservice.v1.NotificationService.Hello
*/
hello: {
name: "Hello",
I: HelloRequest,
O: HelloResponse,
kind: MethodKind.Unary,
},
/**
* HelloStream is for debug purposes
*
* @generated from rpc land.gno.dsocial.notificationservice.v1.NotificationService.HelloStream
*/
helloStream: {
name: "HelloStream",
I: HelloStreamRequest,
O: HelloStreamResponse,
kind: MethodKind.ServerStreaming,
},
}
};

Loading

0 comments on commit 11d53e3

Please sign in to comment.