Skip to content

Commit

Permalink
build out service more
Browse files Browse the repository at this point in the history
  • Loading branch information
jakehobbs committed Jan 17, 2024
1 parent 1e95306 commit d773d86
Show file tree
Hide file tree
Showing 10 changed files with 271 additions and 57 deletions.
1 change: 1 addition & 0 deletions .prettierignore
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
src/routeTree.gen.ts
pnpm-lock.yaml
service
8 changes: 4 additions & 4 deletions service/db.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,21 +13,21 @@ func mustGetOrCreateDb() *sqlx.DB {

db, err := sqlx.Open("mysql", dsn)
if err != nil {
log.Fatalf("Error connecting to database: %q", err)
log.Fatalf("Error connecting to database: %v", err)
}
fmt.Println("Connected to database")

// Create the database if it doesn't exist.
query := fmt.Sprintf("CREATE DATABASE IF NOT EXISTS %s", dbName)
_, err = db.Exec(query)
if err != nil {
log.Fatalf("Error creating database: %q", err)
log.Fatalf("Error creating database: %v", err)
}

// Connect to the specific database.
db, err = sqlx.Open("mysql", config.Dsn)
if err != nil {
log.Fatalf("Error connecting to specified database: %q", err)
log.Fatalf("Error connecting to specified database: %v", err)
}

createTableQuery := `
Expand All @@ -46,7 +46,7 @@ func mustGetOrCreateDb() *sqlx.DB {
)`
_, err = db.Exec(createTableQuery)
if err != nil {
log.Fatalf("Error creating messages table: %q", err)
log.Fatalf("Error creating messages table: %v", err)
}

return db
Expand Down
2 changes: 2 additions & 0 deletions service/go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ require (
)

require (
github.com/aws/aws-sdk-go v1.49.22 // indirect
github.com/go-sql-driver/mysql v1.7.1 // indirect
github.com/jmespath/go-jmespath v0.4.0 // indirect
github.com/jmoiron/sqlx v1.3.5 // indirect
)
10 changes: 10 additions & 0 deletions service/go.sum
Original file line number Diff line number Diff line change
@@ -1,11 +1,21 @@
github.com/aws/aws-sdk-go v1.49.22 h1:r01+cQJ3cORQI1PJxG8af0jzrZpUOL9L+/3kU2x1geU=
github.com/aws/aws-sdk-go v1.49.22/go.mod h1:LF8svs817+Nz+DmiMQKTO3ubZ/6IaTpq3TjupRn3Eqk=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/go-chi/chi/v5 v5.0.11 h1:BnpYbFZ3T3S1WMpD79r7R5ThWX40TaFB7L31Y8xqSwA=
github.com/go-chi/chi/v5 v5.0.11/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8=
github.com/go-chi/cors v1.2.1 h1:xEC8UT3Rlp2QuWNEr4Fs/c2EAGVKBwy/1vHx3bppil4=
github.com/go-chi/cors v1.2.1/go.mod h1:sSbTewc+6wYHBBCW7ytsFSn836hqM7JxpglAy2Vzc58=
github.com/go-sql-driver/mysql v1.6.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg=
github.com/go-sql-driver/mysql v1.7.1 h1:lUIinVbN1DY0xBg0eMOzmmtGoHwWBbvnWubQUrtU8EI=
github.com/go-sql-driver/mysql v1.7.1/go.mod h1:OXbVy3sEdcQ2Doequ6Z5BW6fXNQTmx+9S1MCJN5yJMI=
github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9YPoQUg=
github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo=
github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U=
github.com/jmoiron/sqlx v1.3.5 h1:vFFPA71p1o5gAeqtEAwLU4dnX2napprKtHr7PYIcN3g=
github.com/jmoiron/sqlx v1.3.5/go.mod h1:nRVWtLre0KfCLJvgxzCsLVMogSvQ1zNJtpYr2Ccp0mQ=
github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
github.com/mattn/go-sqlite3 v1.14.6/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
73 changes: 73 additions & 0 deletions service/handlers.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
package main

import (
"database/sql"
"encoding/json"
"fmt"
"github.com/dxe/helptheducks.com/service/model"
"net/http"
"net/mail"
)

type CreateMessageInput struct {
Name string `json:"name"`
Email string `json:"email"`
Phone string `json:"phone,omitempty"`
OutsideUS bool `json:"outside_us"`
Zip string `json:"zip,omitempty"`
City string `json:"city,omitempty"`
Message string `json:"message"`
// TODO: add captcha.
}

func createMessageHandler(w http.ResponseWriter, r *http.Request) {
var body CreateMessageInput

err := json.NewDecoder(r.Body).Decode(&body)
if err != nil {
http.Error(w, fmt.Sprintf("error parsing request body: %v", err), http.StatusBadRequest)
return
}

_, err = mail.ParseAddress(body.Email)
if err != nil {
http.Error(w, fmt.Sprintf("invalid email address: %v", err), http.StatusBadRequest)
return
}

if len(body.Name) == 0 {
http.Error(w, "name is required", http.StatusBadRequest)
return
}

message := model.Message{
Name: body.Name,
Email: body.Email,
Phone: sql.NullString{
String: body.Phone,
Valid: body.Phone != "",
},
OutsideUS: body.OutsideUS,
Zip: sql.NullString{
String: body.Zip,
Valid: body.Zip != "",
},
City: sql.NullString{
String: body.City,
Valid: body.City != "",
},
Message: body.Message,
IPAddress: sql.NullString{
String: r.RemoteAddr,
Valid: r.RemoteAddr != "",
},
}

err = model.InsertMessage(db, message)
if err != nil {
http.Error(w, fmt.Sprintf("error saving message: %v", err), http.StatusInternalServerError)
return
}

w.Write([]byte("ok"))
}
63 changes: 63 additions & 0 deletions service/mailer/mailer.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
package mailer

import (
"fmt"
"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/aws/session"
"github.com/aws/aws-sdk-go/service/ses"
"os"
)

func CreateClient() (*ses.SES, error) {
accessKey := os.Getenv("AWS_ACCESS_KEY_ID")
secretKey := os.Getenv("AWS_SECRET_ACCESS_KEY")
if accessKey == "" || secretKey == "" {
return nil, fmt.Errorf("AWS credentials not set in environment variables")
}

s, err := session.NewSessionWithOptions(session.Options{
Config: aws.Config{
Region: aws.String("us-west-2"),
},
})
if err != nil {
return nil, fmt.Errorf("error creating AWS session: %v", err)
}

return ses.New(s), nil
}

type SendOptions struct {
From string
ReplyTo string
To string
Subject string
Body string
}

func Send(client *ses.SES, options SendOptions) error {
emailInput := &ses.SendEmailInput{
Destination: &ses.Destination{
ToAddresses: []*string{aws.String(options.To)},
},
Message: &ses.Message{
Body: &ses.Body{
Text: &ses.Content{
Data: aws.String(options.Body),
},
},
Subject: &ses.Content{
Data: aws.String(options.Subject),
},
},
Source: aws.String(options.From),
ReplyToAddresses: []*string{aws.String(options.ReplyTo)},
}
result, err := client.SendEmail(emailInput)
if err != nil {
return fmt.Errorf("error sending email: %v", err)
}

fmt.Println("Email sent! Message ID:", *result.MessageId)
return nil
}
85 changes: 67 additions & 18 deletions service/main.go
Original file line number Diff line number Diff line change
@@ -1,53 +1,102 @@
package main

import (
"encoding/json"
"fmt"
"github.com/aws/aws-sdk-go/service/ses"
"github.com/dxe/helptheducks.com/service/config"
"github.com/dxe/helptheducks.com/service/mailer"
"github.com/dxe/helptheducks.com/service/model"
"github.com/go-chi/chi/v5"
"github.com/go-chi/chi/v5/middleware"
"github.com/go-chi/cors"
_ "github.com/go-sql-driver/mysql"
"github.com/jmoiron/sqlx"
"net/http"
"strings"
"time"
)

_ "github.com/go-sql-driver/mysql"
var (
r *chi.Mux
db *sqlx.DB
mailClient *ses.SES
)

func main() {
r := chi.NewRouter()
r = chi.NewRouter()
r.Use(middleware.RequestID)
r.Use(middleware.RealIP)
r.Use(middleware.Logger)
r.Use(middleware.Recoverer)
r.Use(middleware.Timeout(30 * time.Second))
r.Use(cors.Handler(cors.Options{
AllowedOrigins: []string{"http://localhost:5173", "https://helptheducks.com"},
AllowedMethods: []string{"GET", "POST", "PUT", "DELETE", "OPTIONS"},
AllowedMethods: []string{"GET", "POST", "OPTIONS"},
AllowedHeaders: []string{"Accept", "Authorization", "Content-Type"},
AllowCredentials: false,
MaxAge: 300, // Maximum value not ignored by any of major browsers.
}))

db := mustGetOrCreateDb()
db = mustGetOrCreateDb()
defer db.Close()

r.Route("/message", func(r chi.Router) {
r.Post("/create", func(w http.ResponseWriter, r *http.Request) {
// TODO: add captcha.
var message model.Message
err := json.NewDecoder(r.Body).Decode(&message)
if err != nil {
http.Error(w, fmt.Sprintf("error parsing request body: %q", err), http.StatusBadRequest)
return
}
model.InsertMessage(r.Context(), db, message)
w.Write([]byte("ok"))
})
r.Post("/create", createMessageHandler)
})

// TODO: add a worker for sending the messages via SES.
go worker()

fmt.Printf("Listening on port %v", config.Port)
fmt.Printf("Listening on port %v\n", config.Port)
http.ListenAndServe("localhost:"+config.Port, r)
}

func worker() {
var err error
mailClient, err = mailer.CreateClient()
if err != nil {
fmt.Printf("Could not create mail client: %v\n", err)
return
}
for {
processNewMessages()
time.Sleep(60 * time.Second)
}
}

func processNewMessages() {
messages, err := model.GetMessagesToProcess(db)
if err != nil {
fmt.Printf("Error getting messages to process: %v\n", err)
return
}

var success, fail []int

for _, message := range messages {
fmt.Printf("Processing message id: %v\n", message.ID)
fromEmail := strings.Join(strings.Split(strings.ToLower(message.Name), " "), ".") + "@mail.helptheducks.com"
err := mailer.Send(mailClient, mailer.SendOptions{
From: fmt.Sprintf("%s <%s>", message.Name, fromEmail),
ReplyTo: message.Email,
To: "[email protected]", // TODO
Subject: "Testing email", // TODO
Body: message.Message,
})
if err != nil {
fmt.Printf("Error sending email: %v", err)
fail = append(fail, message.ID)
} else {
success = append(success, message.ID)
}
}

err = model.UpdateMessageStatus(db, success, "SENT")
if err != nil {
fmt.Printf("Error updating message status: %v\n", err)
}

err = model.UpdateMessageStatus(db, fail, "FAILED")
if err != nil {
fmt.Printf("Error updating message status: %v\n", err)
}
}
20 changes: 0 additions & 20 deletions service/model/helpers.go

This file was deleted.

Loading

0 comments on commit d773d86

Please sign in to comment.