Skip to content

Commit 016b820

Browse files
authored
forgot password flow (#4)
* complete password registration flow * dont load .env on prod * use TLS in db conn * fix not use SSL * add error logs + better error http response scheme * add config object * tryna see resend_api_key issues * woops * use resend api key from config * don't log it cmon
1 parent e85619b commit 016b820

37 files changed

Lines changed: 1686 additions & 132 deletions

apps/api/.dockerignore

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
# flyctl launch added from .gitignore
2+
**/.idea
3+
**/bin
4+
**/.env
5+
**/gin-bin
6+
fly.toml

apps/api/.env.example

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,4 +4,8 @@ DB_NAME="supernova"
44
DB_USERNAME="root"
55
DB_PASSWORD="example"
66
DB_PORT=5432
7-
JWT_SECRET="A super secret password" # you can use `openssl rand -base64 32` to generate a random string
7+
JWT_SECRET="A super secret password" # you can use `openssl rand -base64 32` to generate a random string
8+
RESEND_API_KEY=""
9+
BASE_URL_WEB_APP="" # for various things that happens not on the desktop
10+
REDIS_URL="127.0.0.1:6379"
11+
REDIS_PASSWORD="example"

apps/api/Dockerfile

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
# Use the official Go base image
2+
FROM golang:1.20 AS build
3+
4+
# Set the working directory inside the container
5+
WORKDIR /app
6+
7+
# Copy the Go application source code into the container
8+
COPY . .
9+
10+
# Build the Go application
11+
RUN go build -o /supernova
12+
13+
# Expose the port that the Go application will listen on
14+
EXPOSE 3001
15+
16+
# Define the command to run your Go application
17+
CMD ["/supernova"]

apps/api/db/main.go

Lines changed: 12 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,8 @@ package db
22

33
import (
44
"fmt"
5-
"os"
65

7-
"github.com/joho/godotenv"
6+
"github.com/trysupernova/supernova-api/utils"
87
"gorm.io/driver/mysql"
98
"gorm.io/gorm"
109
)
@@ -16,20 +15,12 @@ var DB *gorm.DB
1615
* Returns a database connection URL suitable for use with Atlas
1716
*/
1817
func GetDatabaseUrl() string {
19-
// set config file for dev environment only
20-
if os.Getenv("ENVIRONMENT") == "dev" || os.Getenv("ENVIRONMENT") == "" {
21-
err := godotenv.Load(".env")
22-
if err != nil {
23-
panic("Error loading .env file")
24-
}
25-
}
26-
2718
//db config vars
28-
dbHost := os.Getenv("DB_HOST")
29-
dbName := os.Getenv("DB_NAME")
30-
dbUser := os.Getenv("DB_USERNAME")
31-
dbPassword := os.Getenv("DB_PASSWORD")
32-
dbPort := os.Getenv("DB_PORT")
19+
dbHost := utils.GetConfig().DB_HOST
20+
dbName := utils.GetConfig().DB_NAME
21+
dbUser := utils.GetConfig().DB_USERNAME
22+
dbPassword := utils.GetConfig().DB_PASSWORD
23+
dbPort := utils.GetConfig().DB_PORT
3324

3425
//build connection string
3526
var dbConnectionString string = fmt.Sprintf("mysql://%s:%s@%s:%s/%s", dbUser, dbPassword, dbHost, dbPort, dbName)
@@ -41,20 +32,12 @@ func GetDatabaseUrl() string {
4132
* Returns a database connection DSN suitable for use with GORM
4233
*/
4334
func GetDatabaseDSN() string {
44-
// set config file for dev environment only
45-
if os.Getenv("ENVIRONMENT") == "dev" || os.Getenv("ENVIRONMENT") == "" {
46-
err := godotenv.Load(".env")
47-
if err != nil {
48-
panic("Error loading .env file")
49-
}
50-
}
51-
5235
//db config vars
53-
dbHost := os.Getenv("DB_HOST")
54-
dbName := os.Getenv("DB_NAME")
55-
dbUser := os.Getenv("DB_USERNAME")
56-
dbPassword := os.Getenv("DB_PASSWORD")
57-
dbPort := os.Getenv("DB_PORT")
36+
dbHost := utils.GetConfig().DB_HOST
37+
dbName := utils.GetConfig().DB_NAME
38+
dbUser := utils.GetConfig().DB_USERNAME
39+
dbPassword := utils.GetConfig().DB_PASSWORD
40+
dbPort := utils.GetConfig().DB_PORT
5841

5942
//build connection string
6043
var dbConnectionString string = fmt.Sprintf("%s:%s@tcp(%s:%s)/%s?charset=utf8mb4&parseTime=True&loc=Local", dbUser, dbPassword, dbHost, dbPort, dbName)
@@ -66,16 +49,8 @@ func GetDatabaseDSN() string {
6649
* Setup database connection and return a pointer to the connection
6750
*/
6851
func SetupDB() *gorm.DB {
69-
// set config file for dev environment only
70-
if os.Getenv("ENVIRONMENT") == "dev" || os.Getenv("ENVIRONMENT") == "" {
71-
err := godotenv.Load(".env")
72-
if err != nil {
73-
panic("Error loading .env file")
74-
}
75-
}
76-
7752
//build connection string
78-
var dbConnectionString string = GetDatabaseDSN()
53+
dbConnectionString := GetDatabaseDSN()
7954
//connect to db
8055
db, dbError := gorm.Open(mysql.Open(dbConnectionString), &gorm.Config{SkipDefaultTransaction: true})
8156
if dbError != nil {

apps/api/db/redis.go

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
package db
2+
3+
import (
4+
"github.com/redis/go-redis/v9"
5+
"github.com/trysupernova/supernova-api/utils"
6+
)
7+
8+
var Redis *redis.Client
9+
10+
func SetupRedis() *redis.Client {
11+
Redis = redis.NewClient(&redis.Options{
12+
Addr: utils.GetConfig().REDIS_URL,
13+
Password: utils.GetConfig().REDIS_PASSWORD,
14+
DB: 0,
15+
})
16+
return Redis
17+
}

apps/api/docker-compose.yml

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,16 @@ services:
1111
- "3306:3306"
1212
volumes:
1313
- db:/var/lib/mysql
14+
redis:
15+
image: redis:6
16+
container_name: redis
17+
ports:
18+
- "6379:6379"
19+
volumes:
20+
- redis:/data
21+
environment:
22+
- REDIS_PASSWORD=example
1423

1524
volumes:
16-
db:
25+
db:
26+
redis:

apps/api/email/errors.go

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
package email
2+
3+
import "github.com/trysupernova/supernova-api/utils"
4+
5+
const (
6+
ErrEmailSendFailed utils.AppErrorType = "email_send_failed"
7+
ErrEmailCompileFailed utils.AppErrorType = "email_compile_failed"
8+
)

apps/api/email/main.go

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
package email
2+
3+
import (
4+
"bytes"
5+
"context"
6+
"embed"
7+
"text/template"
8+
9+
"github.com/Boostport/mjml-go"
10+
"github.com/resendlabs/resend-go"
11+
"github.com/trysupernova/supernova-api/utils"
12+
)
13+
14+
type EmailClient struct{}
15+
16+
func New() *EmailClient {
17+
return &EmailClient{}
18+
}
19+
20+
type EmailSend struct {
21+
From string
22+
To []string
23+
Subject string
24+
Body string
25+
ReplyTo string
26+
}
27+
28+
func (e *EmailClient) SendEmail(send EmailSend) (string, error) {
29+
apiKey := utils.GetConfig().RESEND_API_KEY
30+
client := resend.NewClient(apiKey)
31+
params := &resend.SendEmailRequest{
32+
To: send.To,
33+
From: send.From,
34+
Html: send.Body,
35+
Subject: send.Subject,
36+
ReplyTo: send.ReplyTo,
37+
}
38+
39+
sent, err := client.Emails.Send(params)
40+
if err != nil {
41+
return "", utils.NewAppError(ErrEmailSendFailed, "Failed to send email: "+err.Error())
42+
}
43+
return sent.Id, nil
44+
}
45+
46+
//go:embed templates/*
47+
var resources embed.FS
48+
49+
var tmpl = template.Must(template.ParseFS(resources, "templates/*"))
50+
51+
func CompileEmailForgotPassword(resetUrl string) (string, error) {
52+
// open a file
53+
// read the contents of the file into a string
54+
var result bytes.Buffer
55+
err := tmpl.Execute(&result, struct {
56+
Url string
57+
}{
58+
Url: resetUrl,
59+
})
60+
if err != nil {
61+
return "", err
62+
}
63+
renderedMjml := result.String()
64+
renderedHtml, err := mjml.ToHTML(context.Background(), renderedMjml, mjml.WithMinify(true))
65+
if err != nil {
66+
return "", utils.NewAppError(ErrEmailSendFailed, "Failed to compile email template to HTML: "+err.Error())
67+
}
68+
return renderedHtml, nil
69+
}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
<mjml>
2+
<mj-body>
3+
<mj-section>
4+
<mj-column>
5+
<mj-text font-size="20px" color="#F45E43" font-family="helvetica">Forgot your password?</mj-text>
6+
<mj-text>Please click on the following link to reset your password:</mj-text>
7+
<mj-text>
8+
<a href="{{ .Url }}" target="_blank">Reset Password</a>
9+
</mj-text>
10+
</mj-column>
11+
</mj-section>
12+
</mj-body>
13+
</mjml>
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
<mjml>
2+
<mj-body>
3+
<mj-section>
4+
<mj-column>
5+
<mj-divider border-color="#F45E43"></mj-divider>
6+
7+
<mj-text font-size="20px" color="#F45E43" font-family="helvetica">Hello World</mj-text>
8+
9+
</mj-column>
10+
</mj-section>
11+
</mj-body>
12+
</mjml>

0 commit comments

Comments
 (0)