✨ Postman the notifier
This commit is contained in:
		
							
								
								
									
										41
									
								
								pkg/internal/grpc/postman.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										41
									
								
								pkg/internal/grpc/postman.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,41 @@ | ||||
| package grpc | ||||
|  | ||||
| import ( | ||||
| 	"context" | ||||
| 	"git.solsynth.dev/hydrogen/dealer/pkg/internal/services" | ||||
| 	"git.solsynth.dev/hydrogen/dealer/pkg/proto" | ||||
| ) | ||||
|  | ||||
| func (v *Server) DeliverNotification(ctx context.Context, request *proto.DeliverNotificationRequest) (*proto.DeliverResponse, error) { | ||||
| 	services.PublishDeliveryTask(request) | ||||
| 	return &proto.DeliverResponse{}, nil | ||||
| } | ||||
|  | ||||
| func (v *Server) DeliverNotificationBatch(ctx context.Context, request *proto.DeliverNotificationBatchRequest) (*proto.DeliverResponse, error) { | ||||
| 	for idx, provider := range request.GetProviders() { | ||||
| 		token := request.GetDeviceTokens()[idx] | ||||
| 		services.PublishDeliveryTask(&proto.DeliverNotificationRequest{ | ||||
| 			Provider:    provider, | ||||
| 			DeviceToken: token, | ||||
| 			Notify:      request.GetNotify(), | ||||
| 		}) | ||||
| 	} | ||||
|  | ||||
| 	return &proto.DeliverResponse{}, nil | ||||
| } | ||||
|  | ||||
| func (v *Server) DeliverEmail(ctx context.Context, request *proto.DeliverEmailRequest) (*proto.DeliverResponse, error) { | ||||
| 	services.PublishDeliveryTask(request) | ||||
| 	return &proto.DeliverResponse{}, nil | ||||
| } | ||||
|  | ||||
| func (v *Server) DeliverEmailBatch(ctx context.Context, request *proto.DeliverEmailBatchRequest) (*proto.DeliverResponse, error) { | ||||
| 	for _, to := range request.GetTo() { | ||||
| 		services.PublishDeliveryTask(&proto.DeliverEmailRequest{ | ||||
| 			To:    to, | ||||
| 			Email: request.GetEmail(), | ||||
| 		}) | ||||
| 	} | ||||
|  | ||||
| 	return &proto.DeliverResponse{}, nil | ||||
| } | ||||
| @@ -16,6 +16,7 @@ import ( | ||||
| type Server struct { | ||||
| 	proto.UnimplementedServiceDirectoryServer | ||||
| 	proto.UnimplementedStreamControllerServer | ||||
| 	proto.UnimplementedPostmanServer | ||||
| 	proto.UnimplementedAuthServer | ||||
|  | ||||
| 	srv *grpc.Server | ||||
| @@ -29,6 +30,7 @@ func NewServer() *Server { | ||||
| 	proto.RegisterServiceDirectoryServer(server.srv, server) | ||||
| 	proto.RegisterStreamControllerServer(server.srv, server) | ||||
| 	proto.RegisterAuthServer(server.srv, server) | ||||
| 	proto.RegisterPostmanServer(server.srv, server) | ||||
| 	health.RegisterHealthServer(server.srv, server) | ||||
|  | ||||
| 	reflection.Register(server.srv) | ||||
|   | ||||
							
								
								
									
										169
									
								
								pkg/internal/services/postman.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										169
									
								
								pkg/internal/services/postman.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,169 @@ | ||||
| package services | ||||
|  | ||||
| import ( | ||||
| 	"context" | ||||
| 	"crypto/tls" | ||||
| 	"firebase.google.com/go/messaging" | ||||
| 	"fmt" | ||||
| 	"git.solsynth.dev/hydrogen/dealer/pkg/proto" | ||||
| 	"github.com/jordan-wright/email" | ||||
| 	"github.com/rs/zerolog/log" | ||||
| 	"github.com/sideshow/apns2" | ||||
| 	payload2 "github.com/sideshow/apns2/payload" | ||||
| 	"github.com/spf13/viper" | ||||
| 	"net/smtp" | ||||
| 	"net/textproto" | ||||
| 	"time" | ||||
| ) | ||||
|  | ||||
| var deliveryTaskQueue = make(chan any, 256) | ||||
|  | ||||
| func PublishDeliveryTask(task any) { | ||||
| 	deliveryTaskQueue <- task | ||||
| } | ||||
|  | ||||
| func ConsumeDeliveryTasks() { | ||||
| 	for { | ||||
| 		task := <-deliveryTaskQueue | ||||
| 		switch tk := task.(type) { | ||||
| 		case *proto.DeliverEmailRequest: | ||||
| 			if tk.GetEmail().HtmlBody != nil { | ||||
| 				_ = SendMailHTML(tk.GetTo(), tk.GetEmail().GetSubject(), tk.GetEmail().GetHtmlBody()) | ||||
| 			} else { | ||||
| 				_ = SendMail(tk.GetTo(), tk.GetEmail().GetSubject(), tk.GetEmail().GetTextBody()) | ||||
| 			} | ||||
| 		case *proto.DeliverNotificationRequest: | ||||
| 			switch tk.GetProvider() { | ||||
| 			case "firebase": | ||||
| 				_ = PushFirebaseNotify(tk.GetDeviceToken(), tk.GetNotify()) | ||||
| 			case "apple": | ||||
| 				_ = PushAppleNotify(tk.GetDeviceToken(), tk.GetNotify()) | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func PushFirebaseNotify(token string, in *proto.NotifyRequest) error { | ||||
| 	if ExtFire == nil { | ||||
| 		return fmt.Errorf("firebase push notification is unavailable") | ||||
| 	} | ||||
|  | ||||
| 	start := time.Now() | ||||
|  | ||||
| 	ctx := context.Background() | ||||
| 	client, err := ExtFire.Messaging(ctx) | ||||
| 	if err != nil { | ||||
| 		return fmt.Errorf("failed to create firebase client") | ||||
| 	} | ||||
|  | ||||
| 	var image string | ||||
| 	if in.Picture != nil { | ||||
| 		image = *in.Picture | ||||
| 	} | ||||
| 	var subtitle string | ||||
| 	if in.Subtitle != nil { | ||||
| 		subtitle = "\n" + *in.Subtitle | ||||
| 	} | ||||
| 	message := &messaging.Message{ | ||||
| 		Notification: &messaging.Notification{ | ||||
| 			Title:    in.Title, | ||||
| 			Body:     subtitle + in.Body, | ||||
| 			ImageURL: image, | ||||
| 		}, | ||||
| 		Token: token, | ||||
| 	} | ||||
|  | ||||
| 	if response, err := client.Send(ctx, message); err != nil { | ||||
| 		log.Warn().Err(err).Msg("An error occurred when notify subscriber via FCM...") | ||||
| 	} else { | ||||
| 		log.Debug(). | ||||
| 			Dur("elapsed", time.Since(start)). | ||||
| 			Str("response", response). | ||||
| 			Msg("Push a notify via firebase") | ||||
| 	} | ||||
|  | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| func PushAppleNotify(token string, in *proto.NotifyRequest) error { | ||||
| 	if ExtAPNS == nil { | ||||
| 		return fmt.Errorf("apple push notification is unavailable") | ||||
| 	} | ||||
|  | ||||
| 	start := time.Now() | ||||
|  | ||||
| 	data := payload2. | ||||
| 		NewPayload(). | ||||
| 		AlertTitle(in.GetTitle()). | ||||
| 		AlertBody(in.GetBody()). | ||||
| 		Category(in.GetTopic()). | ||||
| 		Sound("default"). | ||||
| 		MutableContent() | ||||
| 	if in.Avatar != nil { | ||||
| 		data = data.Custom("avatar", *in.Avatar) | ||||
| 	} | ||||
| 	if in.Picture != nil { | ||||
| 		data = data.Custom("picture", *in.Picture) | ||||
| 	} | ||||
| 	rawData, err := data.MarshalJSON() | ||||
| 	if err != nil { | ||||
| 		log.Warn().Err(err).Msg("An error occurred when preparing to notify subscriber via APNs...") | ||||
| 	} | ||||
| 	payload := &apns2.Notification{ | ||||
| 		DeviceToken: token, | ||||
| 		Topic:       viper.GetString("apns_topic"), | ||||
| 		Payload:     rawData, | ||||
| 	} | ||||
|  | ||||
| 	if resp, err := ExtAPNS.Push(payload); err != nil { | ||||
| 		log.Warn().Err(err).Msg("An error occurred when notify subscriber via APNs...") | ||||
| 	} else { | ||||
| 		log.Debug(). | ||||
| 			Dur("elapsed", time.Since(start)). | ||||
| 			Str("reason", resp.Reason). | ||||
| 			Int("status", resp.StatusCode). | ||||
| 			Msg("Push a notify via firebase") | ||||
| 	} | ||||
|  | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| func SendMail(target string, subject string, content string) error { | ||||
| 	mail := &email.Email{ | ||||
| 		To:      []string{target}, | ||||
| 		From:    viper.GetString("mailer.name"), | ||||
| 		Subject: subject, | ||||
| 		Text:    []byte(content), | ||||
| 		Headers: textproto.MIMEHeader{}, | ||||
| 	} | ||||
| 	return mail.SendWithTLS( | ||||
| 		fmt.Sprintf("%s:%d", viper.GetString("mailer.smtp_host"), viper.GetInt("mailer.smtp_port")), | ||||
| 		smtp.PlainAuth( | ||||
| 			"", | ||||
| 			viper.GetString("mailer.username"), | ||||
| 			viper.GetString("mailer.password"), | ||||
| 			viper.GetString("mailer.smtp_host"), | ||||
| 		), | ||||
| 		&tls.Config{ServerName: viper.GetString("mailer.smtp_host")}, | ||||
| 	) | ||||
| } | ||||
|  | ||||
| func SendMailHTML(target string, subject string, content string) error { | ||||
| 	mail := &email.Email{ | ||||
| 		To:      []string{target}, | ||||
| 		From:    viper.GetString("mailer.name"), | ||||
| 		Subject: subject, | ||||
| 		HTML:    []byte(content), | ||||
| 		Headers: textproto.MIMEHeader{}, | ||||
| 	} | ||||
| 	return mail.SendWithTLS( | ||||
| 		fmt.Sprintf("%s:%d", viper.GetString("mailer.smtp_host"), viper.GetInt("mailer.smtp_port")), | ||||
| 		smtp.PlainAuth( | ||||
| 			"", | ||||
| 			viper.GetString("mailer.username"), | ||||
| 			viper.GetString("mailer.password"), | ||||
| 			viper.GetString("mailer.smtp_host"), | ||||
| 		), | ||||
| 		&tls.Config{ServerName: viper.GetString("mailer.smtp_host")}, | ||||
| 	) | ||||
| } | ||||
							
								
								
									
										43
									
								
								pkg/internal/services/pusher_conn.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										43
									
								
								pkg/internal/services/pusher_conn.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,43 @@ | ||||
| package services | ||||
|  | ||||
| import ( | ||||
| 	"context" | ||||
| 	firebase "firebase.google.com/go" | ||||
| 	"github.com/sideshow/apns2" | ||||
| 	"github.com/sideshow/apns2/token" | ||||
| 	"github.com/spf13/viper" | ||||
| 	"google.golang.org/api/option" | ||||
| ) | ||||
|  | ||||
| // ExtFire is a Firebase App client | ||||
| var ExtFire *firebase.App | ||||
|  | ||||
| func SetupFirebase() error { | ||||
| 	opt := option.WithCredentialsFile(viper.GetString("firebase_credentials")) | ||||
| 	app, err := firebase.NewApp(context.Background(), nil, opt) | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} else { | ||||
| 		ExtFire = app | ||||
| 	} | ||||
|  | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| // ExtAPNS is an Apple Push Notification Services client | ||||
| var ExtAPNS *apns2.Client | ||||
|  | ||||
| func SetupAPNS() error { | ||||
| 	authKey, err := token.AuthKeyFromFile(viper.GetString("apns_credentials")) | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
|  | ||||
| 	ExtAPNS = apns2.NewTokenClient(&token.Token{ | ||||
| 		AuthKey: authKey, | ||||
| 		KeyID:   viper.GetString("apns_credentials_key"), | ||||
| 		TeamID:  viper.GetString("apns_credentials_team"), | ||||
| 	}).Production() | ||||
|  | ||||
| 	return nil | ||||
| } | ||||
		Reference in New Issue
	
	Block a user