feat: mailer internal, templates, bruno cleanup, create user, send welcome mail, mailtrap
This commit is contained in:
@@ -175,3 +175,19 @@ func (app *application) readInt(qs url.Values, key string, defaultValue int, v *
|
||||
// Otherwise, return the converted integer value.
|
||||
return i
|
||||
}
|
||||
|
||||
// The background() helper accepts an arbitrary function as a parameter.
|
||||
func (app *application) background(fn func()) {
|
||||
// Launch a background goroutine.
|
||||
go func() {
|
||||
// Recover any panic.
|
||||
defer func() {
|
||||
if err := recover(); err != nil {
|
||||
app.logger.PrintError(fmt.Errorf("%s", err), nil)
|
||||
}
|
||||
}()
|
||||
|
||||
// Execute the arbitrary function that we passed as the parameter.
|
||||
fn()
|
||||
}()
|
||||
}
|
||||
|
||||
@@ -11,6 +11,7 @@ import (
|
||||
_ "github.com/lib/pq"
|
||||
"greenlight.debuggingjon.dev/internal/data"
|
||||
"greenlight.debuggingjon.dev/internal/jsonlog"
|
||||
"greenlight.debuggingjon.dev/internal/mailer"
|
||||
)
|
||||
|
||||
// Declare a string containing the application version number. Later in the book we'll
|
||||
@@ -39,6 +40,13 @@ type config struct {
|
||||
burst int
|
||||
enabled bool
|
||||
}
|
||||
smtp struct {
|
||||
host string
|
||||
port int
|
||||
username string
|
||||
password string
|
||||
sender string
|
||||
}
|
||||
}
|
||||
|
||||
// Define an application struct to hold the dependencies for our HTTP handlers, helpers,
|
||||
@@ -48,6 +56,7 @@ type application struct {
|
||||
config config
|
||||
logger *jsonlog.Logger
|
||||
models data.Models
|
||||
mailer mailer.Mailer
|
||||
}
|
||||
|
||||
func main() {
|
||||
@@ -80,6 +89,16 @@ func main() {
|
||||
flag.IntVar(&cfg.limiter.burst, "limiter-burst", 4, "Rate limiter maximum burst")
|
||||
flag.BoolVar(&cfg.limiter.enabled, "limiter-enabled", true, "Enable rate limiter")
|
||||
|
||||
// Read the SMTP server configuration settings into the config struct, using the
|
||||
// Mailtrap settings as the default values. IMPORTANT: If you're following along,
|
||||
// make sure to replace the default values for smtp-username and smtp-password
|
||||
// with your own Mailtrap credentials.
|
||||
flag.StringVar(&cfg.smtp.host, "smtp-host", "sandbox.smtp.mailtrap.io", "SMTP host")
|
||||
flag.IntVar(&cfg.smtp.port, "smtp-port", 25, "SMTP port")
|
||||
flag.StringVar(&cfg.smtp.username, "smtp-username", "3d946287da35ea", "SMTP username")
|
||||
flag.StringVar(&cfg.smtp.password, "smtp-password", "d06a774b484ca3", "SMTP password")
|
||||
flag.StringVar(&cfg.smtp.sender, "smtp-sender", "Greenlight <no-reply@greenlight.debuggingjon.dev>", "SMTP sender")
|
||||
|
||||
flag.Parse()
|
||||
|
||||
// Call the openDB() helper function (see below) to create the connection pool,
|
||||
@@ -105,6 +124,7 @@ func main() {
|
||||
config: cfg,
|
||||
logger: logger,
|
||||
models: data.NewModels(db),
|
||||
mailer: mailer.New(cfg.smtp.host, cfg.smtp.port, cfg.smtp.username, cfg.smtp.password, cfg.smtp.sender),
|
||||
}
|
||||
|
||||
err = app.serve()
|
||||
|
||||
@@ -23,6 +23,7 @@ func (app *application) routes() http.Handler {
|
||||
router.HandlerFunc(http.MethodGet, "/v1/movies/:id", app.showMovieHandler)
|
||||
router.HandlerFunc(http.MethodPatch, "/v1/movies/:id", app.updateMovieHandler)
|
||||
router.HandlerFunc(http.MethodDelete, "/v1/movies/:id", app.deleteMovieHandler)
|
||||
router.HandlerFunc(http.MethodPost, "/v1/users", app.registerUserHandler)
|
||||
|
||||
// Wrap the router with the rateLimit() middleware.
|
||||
return app.recoverPanic(app.rateLimit(router))
|
||||
|
||||
@@ -0,0 +1,85 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"net/http"
|
||||
|
||||
"greenlight.debuggingjon.dev/internal/data"
|
||||
"greenlight.debuggingjon.dev/internal/validator"
|
||||
)
|
||||
|
||||
func (app *application) registerUserHandler(w http.ResponseWriter, r *http.Request) {
|
||||
// Create an anonymous struct to hold the expected data from the request body.
|
||||
var input struct {
|
||||
Name string `json:"name"`
|
||||
Email string `json:"email"`
|
||||
Password string `json:"password"`
|
||||
}
|
||||
|
||||
// Parse the request body into the anonymous struct.
|
||||
err := app.readJSON(w, r, &input)
|
||||
if err != nil {
|
||||
app.badRequestResponse(w, r, err)
|
||||
return
|
||||
}
|
||||
|
||||
// Copy the data from the request body into a new User struct. Notice also that we
|
||||
// set the Activated field to false, which isn't strictly necessary because the
|
||||
// Activated field will have the zero-value of false by default. But setting this
|
||||
// explicitly helps to make our intentions clear to anyone reading the code.
|
||||
user := &data.User{
|
||||
Name: input.Name,
|
||||
Email: input.Email,
|
||||
Activated: false,
|
||||
}
|
||||
|
||||
// Use the Password.Set() method to generate and store the hashed and plaintext
|
||||
// passwords.
|
||||
err = user.Password.Set(input.Password)
|
||||
if err != nil {
|
||||
app.serverErrorResponse(w, r, err)
|
||||
return
|
||||
}
|
||||
|
||||
v := validator.New()
|
||||
|
||||
// Validate the user struct and return the error messages to the client if any of
|
||||
// the checks fail.
|
||||
if data.ValidateUser(v, user); !v.Valid() {
|
||||
app.failedValidationResponse(w, r, v.Errors)
|
||||
return
|
||||
}
|
||||
|
||||
// Insert the user data into the database.
|
||||
err = app.models.Users.Insert(user)
|
||||
if err != nil {
|
||||
switch {
|
||||
// If we get a ErrDuplicateEmail error, use the v.AddError() method to manually
|
||||
// add a message to the validator instance, and then call our
|
||||
// failedValidationResponse() helper.
|
||||
case errors.Is(err, data.ErrDuplicateEmail):
|
||||
v.AddError("email", "a user with this email address already exists")
|
||||
app.failedValidationResponse(w, r, v.Errors)
|
||||
default:
|
||||
app.serverErrorResponse(w, r, err)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Use the background helper to execute an anonymous function that sends the welcome
|
||||
// email.
|
||||
app.background(func() {
|
||||
err = app.mailer.Send(user.Email, "user_welcome.tmpl", user)
|
||||
if err != nil {
|
||||
app.logger.PrintError(err, nil)
|
||||
}
|
||||
})
|
||||
|
||||
// Note that we also change this to send the client a 202 Accepted status code.
|
||||
// This status code indicates that the request has been accepted for processing, but
|
||||
// the processing has not been completed.
|
||||
err = app.writeJSON(w, http.StatusAccepted, envelope{"user": user}, nil)
|
||||
if err != nil {
|
||||
app.serverErrorResponse(w, r, err)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user