@@ -4,8 +4,11 @@ import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"html"
|
||||
"log/slog"
|
||||
"strings"
|
||||
|
||||
pbaero "github.com/flowy-live/llink/genproto/aero"
|
||||
"github.com/flowy-live/llink/internal/utils"
|
||||
"github.com/jackc/pgx/v5/pgxpool"
|
||||
)
|
||||
@@ -38,11 +41,12 @@ type Service interface {
|
||||
}
|
||||
|
||||
type serviceImpl struct {
|
||||
repo repository
|
||||
repo repository
|
||||
aeroSvc pbaero.PrimaryClient
|
||||
}
|
||||
|
||||
func NewService(pool *pgxpool.Pool) Service {
|
||||
return &serviceImpl{repo: newRepository(pool)}
|
||||
func NewService(pool *pgxpool.Pool, aeroSvc pbaero.PrimaryClient) Service {
|
||||
return &serviceImpl{repo: newRepository(pool), aeroSvc: aeroSvc}
|
||||
}
|
||||
|
||||
func (s *serviceImpl) Create(ctx context.Context, name, adminHumanId string) (*Network, error) {
|
||||
@@ -125,6 +129,11 @@ func (s *serviceImpl) ListAll(ctx context.Context) ([]*Network, error) {
|
||||
// Invitation methods
|
||||
|
||||
func (s *serviceImpl) InviteByEmail(ctx context.Context, networkID string, emails []string) error {
|
||||
network, err := s.repo.getByID(ctx, networkID)
|
||||
if err != nil {
|
||||
return ErrNotFound
|
||||
}
|
||||
|
||||
for _, email := range emails {
|
||||
normalized, err := utils.NormalizeEmail(email)
|
||||
if err != nil {
|
||||
@@ -133,6 +142,19 @@ func (s *serviceImpl) InviteByEmail(ctx context.Context, networkID string, email
|
||||
if err := s.repo.createInvitation(ctx, networkID, normalized); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = s.aeroSvc.ShootEmail(ctx, &pbaero.ShootEmailRequest{
|
||||
ToEmails: []string{email},
|
||||
Subject: fmt.Sprintf("Invitation to Join %s on Flowy.llink", network.Name),
|
||||
TemplateData: &pbaero.ShootEmailRequest_SimpleHtmlData{
|
||||
SimpleHtmlData: &pbaero.SimpleHtmlData{
|
||||
Html: buildInvitationHTML(network.Name),
|
||||
},
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
slog.Warn("unable to send email notification", "email", email, "network", network.Name)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -171,3 +193,38 @@ func (s *serviceImpl) RevokeInvitation(ctx context.Context, networkID, email str
|
||||
}
|
||||
return s.repo.deleteInvitation(ctx, networkID, normalized)
|
||||
}
|
||||
|
||||
func buildInvitationHTML(networkName string) string {
|
||||
const downloadURL = "https://flowylabs.ai/llink"
|
||||
safeName := html.EscapeString(networkName)
|
||||
return fmt.Sprintf(`<!DOCTYPE html>
|
||||
<html>
|
||||
<body style="margin:0;padding:0;background-color:#f5f5f7;font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,sans-serif;">
|
||||
<table role="presentation" width="100%%" cellspacing="0" cellpadding="0" border="0" style="background-color:#f5f5f7;padding:48px 16px;">
|
||||
<tr>
|
||||
<td align="center">
|
||||
<table role="presentation" width="100%%" cellspacing="0" cellpadding="0" border="0" style="max-width:480px;background-color:#ffffff;border-radius:12px;padding:40px;">
|
||||
<tr>
|
||||
<td style="font-size:22px;font-weight:600;color:#111111;padding-bottom:16px;">
|
||||
You've been invited to join %s on Flowy.llink
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td style="font-size:15px;line-height:1.5;color:#444444;padding-bottom:32px;">
|
||||
Download the app to accept your invitation and connect with your team.
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<a href="%s" style="display:inline-block;background-color:#111111;color:#ffffff;text-decoration:none;font-size:15px;font-weight:500;padding:12px 24px;border-radius:8px;">
|
||||
Download Flowy.llink
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</body>
|
||||
</html>`, safeName, downloadURL)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user