lifedebugger commited on
Commit
c11601b
·
1 Parent(s): 78ef442

Deploy files from GitHub repository

Browse files
config/supabase_config.go CHANGED
@@ -1,9 +1,18 @@
1
  package config
2
 
 
 
 
 
 
 
 
 
3
  type SupabaseConfig interface {
4
  GetURL() string
5
  GetServiceKey() string
6
  GetBucketName() string
 
7
  GetSMTPHost() string
8
  GetSMTPPort() string
9
  GetSMTPUsername() string
@@ -12,34 +21,33 @@ type SupabaseConfig interface {
12
  }
13
 
14
  type supabaseConfig struct {
15
- url string
16
- serviceKey string
17
- bucketName string
18
- smtpHost string
19
- smtpPort string
20
- smtpUsername string
21
- smtpPassword string
22
- smtpSender string
23
  }
24
 
25
  func NewSupabaseConfig(url string, key string, bucket string, smtpHost string, smtpPort string, smtpUsername string, smtpPassword string, smtpSender string) SupabaseConfig {
26
  return &supabaseConfig{
27
- url: url,
28
- serviceKey: key,
29
- bucketName: bucket,
30
- smtpHost: smtpHost,
31
- smtpPort: smtpPort,
32
- smtpUsername: smtpUsername,
33
- smtpPassword: smtpPassword,
34
- smtpSender: smtpSender,
 
 
35
  }
36
  }
37
 
38
- func (c *supabaseConfig) GetURL() string { return c.url }
39
- func (c *supabaseConfig) GetServiceKey() string { return c.serviceKey }
40
- func (c *supabaseConfig) GetBucketName() string { return c.bucketName }
41
- func (c *supabaseConfig) GetSMTPHost() string { return c.smtpHost }
42
- func (c *supabaseConfig) GetSMTPPort() string { return c.smtpPort }
43
- func (c *supabaseConfig) GetSMTPUsername() string { return c.smtpUsername }
44
- func (c *supabaseConfig) GetSMTPPassword() string { return c.smtpPassword }
45
- func (c *supabaseConfig) GetSMTPSender() string { return c.smtpSender }
 
 
1
  package config
2
 
3
+ type SupabaseSMTPConfig struct {
4
+ Host string
5
+ Port string
6
+ Username string
7
+ Password string
8
+ Sender string
9
+ }
10
+
11
  type SupabaseConfig interface {
12
  GetURL() string
13
  GetServiceKey() string
14
  GetBucketName() string
15
+ GetSMTP() SupabaseSMTPConfig
16
  GetSMTPHost() string
17
  GetSMTPPort() string
18
  GetSMTPUsername() string
 
21
  }
22
 
23
  type supabaseConfig struct {
24
+ url string
25
+ serviceKey string
26
+ bucketName string
27
+ smtp SupabaseSMTPConfig
 
 
 
 
28
  }
29
 
30
  func NewSupabaseConfig(url string, key string, bucket string, smtpHost string, smtpPort string, smtpUsername string, smtpPassword string, smtpSender string) SupabaseConfig {
31
  return &supabaseConfig{
32
+ url: url,
33
+ serviceKey: key,
34
+ bucketName: bucket,
35
+ smtp: SupabaseSMTPConfig{
36
+ Host: smtpHost,
37
+ Port: smtpPort,
38
+ Username: smtpUsername,
39
+ Password: smtpPassword,
40
+ Sender: smtpSender,
41
+ },
42
  }
43
  }
44
 
45
+ func (c *supabaseConfig) GetURL() string { return c.url }
46
+ func (c *supabaseConfig) GetServiceKey() string { return c.serviceKey }
47
+ func (c *supabaseConfig) GetBucketName() string { return c.bucketName }
48
+ func (c *supabaseConfig) GetSMTP() SupabaseSMTPConfig { return c.smtp }
49
+ func (c *supabaseConfig) GetSMTPHost() string { return c.smtp.Host }
50
+ func (c *supabaseConfig) GetSMTPPort() string { return c.smtp.Port }
51
+ func (c *supabaseConfig) GetSMTPUsername() string { return c.smtp.Username }
52
+ func (c *supabaseConfig) GetSMTPPassword() string { return c.smtp.Password }
53
+ func (c *supabaseConfig) GetSMTPSender() string { return c.smtp.Sender }
services/supabase_email_verification_service.go CHANGED
@@ -1,18 +1,17 @@
1
  package services
2
 
3
  import (
 
4
  "context"
5
- "crypto/tls"
6
  "fmt"
7
- "net"
8
- "net/mail"
9
- "net/smtp"
10
  "strings"
11
  "time"
12
 
13
  "abdanhafidz.com/go-boilerplate/config"
14
  http_error "abdanhafidz.com/go-boilerplate/models/error"
15
- storage_go "github.com/supabase-community/storage-go"
16
  )
17
 
18
  type SupabaseEmailVerificationService interface {
@@ -20,40 +19,33 @@ type SupabaseEmailVerificationService interface {
20
  }
21
 
22
  type supabaseEmailVerificationService struct {
23
- smtpHost string
24
- smtpPort string
25
- smtpUser string
26
- smtpPass string
27
- smtpSender string
28
- client *storage_go.Client
29
  }
30
 
31
  const (
32
  verificationEmailSubject = "Kode OTP Verifikasi Email Quzuu"
33
  verificationEmailBodyFmt = "Kode OTP kamu adalah %s. Kode ini berlaku selama 15 menit."
 
34
  )
35
 
36
  func NewSupabaseEmailVerificationService(supabaseConfig config.SupabaseConfig) SupabaseEmailVerificationService {
37
  baseURL := strings.TrimRight(strings.TrimSpace(supabaseConfig.GetURL()), "/")
38
  serviceKey := strings.TrimSpace(supabaseConfig.GetServiceKey())
39
-
40
- var client *storage_go.Client
41
- if baseURL != "" && serviceKey != "" {
42
- client = storage_go.NewClient(baseURL+"/storage/v1", serviceKey, nil)
43
- }
44
-
45
- port := strings.TrimSpace(supabaseConfig.GetSMTPPort())
46
- if port == "" {
47
- port = "587"
48
- }
49
 
50
  return &supabaseEmailVerificationService{
51
- smtpHost: strings.TrimSpace(supabaseConfig.GetSMTPHost()),
52
- smtpPort: port,
53
- smtpUser: strings.TrimSpace(supabaseConfig.GetSMTPUsername()),
54
- smtpPass: strings.TrimSpace(supabaseConfig.GetSMTPPassword()),
55
- smtpSender: strings.TrimSpace(supabaseConfig.GetSMTPSender()),
56
- client: client,
57
  }
58
  }
59
 
@@ -62,128 +54,86 @@ func (s *supabaseEmailVerificationService) SendEmailVerification(ctx context.Con
62
  if trimmedEmail == "" {
63
  return http_error.BAD_REQUEST_ERROR
64
  }
65
- if !s.isSMTPConfigured() {
66
- return fmt.Errorf("%w: SMTP config is incomplete", http_error.SEND_EMAIL_FAILED)
 
 
 
67
  }
68
 
69
  tokenCode := fmt.Sprintf("%06d", token)
70
- return s.sendViaSMTP(ctx, trimmedEmail, tokenCode)
 
 
 
 
 
 
 
 
 
 
 
 
71
  }
72
 
73
  func (s *supabaseEmailVerificationService) isSMTPConfigured() bool {
74
- return strings.TrimSpace(s.smtpHost) != "" &&
75
- strings.TrimSpace(s.smtpUser) != "" &&
76
- strings.TrimSpace(s.smtpPass) != "" &&
77
- strings.TrimSpace(s.smtpSender) != ""
 
78
  }
79
 
80
- func (s *supabaseEmailVerificationService) sendViaSMTP(ctx context.Context, email string, tokenCode string) error {
81
  select {
82
  case <-ctx.Done():
83
  return http_error.TIMEOUT
84
  default:
85
  }
86
 
87
- host := strings.TrimSpace(s.smtpHost)
88
- port := strings.TrimSpace(s.smtpPort)
89
-
90
- subject := verificationEmailSubject
91
- body := fmt.Sprintf(verificationEmailBodyFmt, tokenCode)
92
- fromHeader := strings.TrimSpace(s.smtpSender)
93
- fromEnvelope := fromHeader
94
-
95
- if parsedFrom, err := mail.ParseAddress(fromHeader); err == nil {
96
- fromEnvelope = parsedFrom.Address
97
  }
98
 
99
- message := strings.Join([]string{
100
- fmt.Sprintf("From: %s", fromHeader),
101
- fmt.Sprintf("To: %s", email),
102
- fmt.Sprintf("Subject: %s", subject),
103
- "MIME-Version: 1.0",
104
- `Content-Type: text/plain; charset="UTF-8"`,
105
- "",
106
- body,
107
- }, "\r\n")
108
-
109
- client, err := s.newSMTPClient(ctx, host, port)
110
  if err != nil {
111
- if ctx.Err() != nil {
112
- return http_error.TIMEOUT
113
- }
114
- return fmt.Errorf("%w: SMTP dial failed (%s:%s): %v", http_error.SEND_EMAIL_FAILED, host, port, err)
115
- }
116
- defer client.Close()
117
-
118
- auth := smtp.PlainAuth("", s.smtpUser, s.smtpPass, host)
119
- if err := client.Auth(auth); err != nil {
120
- return fmt.Errorf("%w: SMTP auth failed: %v", http_error.SEND_EMAIL_FAILED, err)
121
- }
122
- if err := client.Mail(fromEnvelope); err != nil {
123
- return fmt.Errorf("%w: SMTP MAIL FROM failed (%s): %v", http_error.SEND_EMAIL_FAILED, fromEnvelope, err)
124
- }
125
- if err := client.Rcpt(email); err != nil {
126
- return fmt.Errorf("%w: SMTP RCPT TO failed (%s): %v", http_error.SEND_EMAIL_FAILED, email, err)
127
  }
128
 
129
- dataWriter, err := client.Data()
 
130
  if err != nil {
131
- return fmt.Errorf("%w: SMTP DATA open failed: %v", http_error.SEND_EMAIL_FAILED, err)
132
- }
133
- if _, err := dataWriter.Write([]byte(message)); err != nil {
134
- _ = dataWriter.Close()
135
- return fmt.Errorf("%w: SMTP DATA write failed: %v", http_error.SEND_EMAIL_FAILED, err)
136
- }
137
- if err := dataWriter.Close(); err != nil {
138
- return fmt.Errorf("%w: SMTP DATA close failed: %v", http_error.SEND_EMAIL_FAILED, err)
139
- }
140
-
141
- if err := client.Quit(); err != nil {
142
- return fmt.Errorf("%w: SMTP QUIT failed: %v", http_error.SEND_EMAIL_FAILED, err)
143
- }
144
- return nil
145
- }
146
-
147
- func (s *supabaseEmailVerificationService) newSMTPClient(ctx context.Context, host string, port string) (*smtp.Client, error) {
148
- addr := net.JoinHostPort(host, port)
149
- dialer := &net.Dialer{Timeout: 5 * time.Minute}
150
-
151
- if port == "465" {
152
- conn, err := tls.DialWithDialer(dialer, "tcp", addr, &tls.Config{
153
- ServerName: host,
154
- MinVersion: tls.VersionTLS12,
155
- })
156
- if err != nil {
157
- return nil, err
158
- }
159
- client, err := smtp.NewClient(conn, host)
160
- if err != nil {
161
- _ = conn.Close()
162
- return nil, err
163
- }
164
- return client, nil
165
  }
 
 
 
166
 
167
- conn, err := dialer.DialContext(ctx, "tcp", addr)
168
  if err != nil {
169
- return nil, err
 
 
 
170
  }
 
171
 
172
- client, err := smtp.NewClient(conn, host)
173
- if err != nil {
174
- _ = conn.Close()
175
- return nil, err
176
  }
177
 
178
- if ok, _ := client.Extension("STARTTLS"); ok {
179
- if err := client.StartTLS(&tls.Config{
180
- ServerName: host,
181
- MinVersion: tls.VersionTLS12,
182
- }); err != nil {
183
- _ = client.Close()
184
- return nil, err
185
- }
186
  }
187
 
188
- return client, nil
189
  }
 
1
  package services
2
 
3
  import (
4
+ "bytes"
5
  "context"
6
+ "encoding/json"
7
  "fmt"
8
+ "io"
9
+ "net/http"
 
10
  "strings"
11
  "time"
12
 
13
  "abdanhafidz.com/go-boilerplate/config"
14
  http_error "abdanhafidz.com/go-boilerplate/models/error"
 
15
  )
16
 
17
  type SupabaseEmailVerificationService interface {
 
19
  }
20
 
21
  type supabaseEmailVerificationService struct {
22
+ supabaseURL string
23
+ serviceKey string
24
+ smtp config.SupabaseSMTPConfig
25
+ httpClient *http.Client
 
 
26
  }
27
 
28
  const (
29
  verificationEmailSubject = "Kode OTP Verifikasi Email Quzuu"
30
  verificationEmailBodyFmt = "Kode OTP kamu adalah %s. Kode ini berlaku selama 15 menit."
31
+ supabaseEmailOTPPath = "/auth/v1/otp"
32
  )
33
 
34
  func NewSupabaseEmailVerificationService(supabaseConfig config.SupabaseConfig) SupabaseEmailVerificationService {
35
  baseURL := strings.TrimRight(strings.TrimSpace(supabaseConfig.GetURL()), "/")
36
  serviceKey := strings.TrimSpace(supabaseConfig.GetServiceKey())
37
+ smtp := supabaseConfig.GetSMTP()
38
+ smtp.Host = strings.TrimSpace(smtp.Host)
39
+ smtp.Port = strings.TrimSpace(smtp.Port)
40
+ smtp.Username = strings.TrimSpace(smtp.Username)
41
+ smtp.Password = strings.TrimSpace(smtp.Password)
42
+ smtp.Sender = strings.TrimSpace(smtp.Sender)
 
 
 
 
43
 
44
  return &supabaseEmailVerificationService{
45
+ supabaseURL: baseURL,
46
+ serviceKey: serviceKey,
47
+ smtp: smtp,
48
+ httpClient: &http.Client{Timeout: 15 * time.Second},
 
 
49
  }
50
  }
51
 
 
54
  if trimmedEmail == "" {
55
  return http_error.BAD_REQUEST_ERROR
56
  }
57
+ if !s.isSupabaseConfigured() {
58
+ return fmt.Errorf("%w: Supabase email configuration is incomplete", http_error.SEND_EMAIL_FAILED)
59
+ }
60
+ if s.hasAnySMTPConfig() && !s.isSMTPConfigured() {
61
+ return fmt.Errorf("%w: Supabase SMTP configuration is incomplete", http_error.SEND_EMAIL_FAILED)
62
  }
63
 
64
  tokenCode := fmt.Sprintf("%06d", token)
65
+ return s.sendViaSupabase(ctx, trimmedEmail, tokenCode)
66
+ }
67
+
68
+ func (s *supabaseEmailVerificationService) isSupabaseConfigured() bool {
69
+ return strings.TrimSpace(s.supabaseURL) != "" && strings.TrimSpace(s.serviceKey) != ""
70
+ }
71
+
72
+ func (s *supabaseEmailVerificationService) hasAnySMTPConfig() bool {
73
+ return strings.TrimSpace(s.smtp.Host) != "" ||
74
+ strings.TrimSpace(s.smtp.Port) != "" ||
75
+ strings.TrimSpace(s.smtp.Username) != "" ||
76
+ strings.TrimSpace(s.smtp.Password) != "" ||
77
+ strings.TrimSpace(s.smtp.Sender) != ""
78
  }
79
 
80
  func (s *supabaseEmailVerificationService) isSMTPConfigured() bool {
81
+ return strings.TrimSpace(s.smtp.Host) != "" &&
82
+ strings.TrimSpace(s.smtp.Port) != "" &&
83
+ strings.TrimSpace(s.smtp.Username) != "" &&
84
+ strings.TrimSpace(s.smtp.Password) != "" &&
85
+ strings.TrimSpace(s.smtp.Sender) != ""
86
  }
87
 
88
+ func (s *supabaseEmailVerificationService) sendViaSupabase(ctx context.Context, email string, tokenCode string) error {
89
  select {
90
  case <-ctx.Done():
91
  return http_error.TIMEOUT
92
  default:
93
  }
94
 
95
+ payload := map[string]any{
96
+ "email": email,
97
+ "create_user": true,
98
+ "data": map[string]string{
99
+ "verification_subject": verificationEmailSubject,
100
+ "verification_message": fmt.Sprintf(verificationEmailBodyFmt, tokenCode),
101
+ "verification_code": tokenCode,
102
+ },
 
 
103
  }
104
 
105
+ body, err := json.Marshal(payload)
 
 
 
 
 
 
 
 
 
 
106
  if err != nil {
107
+ return http_error.INTERNAL_SERVER_ERROR
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
108
  }
109
 
110
+ requestURL := s.supabaseURL + supabaseEmailOTPPath
111
+ req, err := http.NewRequestWithContext(ctx, http.MethodPost, requestURL, bytes.NewReader(body))
112
  if err != nil {
113
+ return http_error.INTERNAL_SERVER_ERROR
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
114
  }
115
+ req.Header.Set("Content-Type", "application/json")
116
+ req.Header.Set("apikey", s.serviceKey)
117
+ req.Header.Set("Authorization", "Bearer "+s.serviceKey)
118
 
119
+ res, err := s.httpClient.Do(req)
120
  if err != nil {
121
+ if ctx.Err() != nil {
122
+ return http_error.TIMEOUT
123
+ }
124
+ return fmt.Errorf("%w: Supabase email request failed: %v", http_error.SEND_EMAIL_FAILED, err)
125
  }
126
+ defer res.Body.Close()
127
 
128
+ if res.StatusCode >= http.StatusOK && res.StatusCode < http.StatusMultipleChoices {
129
+ return nil
 
 
130
  }
131
 
132
+ errBody, _ := io.ReadAll(io.LimitReader(res.Body, 8*1024))
133
+ detail := strings.TrimSpace(string(errBody))
134
+ if detail == "" {
135
+ detail = res.Status
 
 
 
 
136
  }
137
 
138
+ return fmt.Errorf("%w: Supabase email request rejected (%d): %s", http_error.SEND_EMAIL_FAILED, res.StatusCode, detail)
139
  }