1
0
mirror of https://github.com/raseels-repos/golang-saas-starter-kit.git synced 2025-06-06 23:46:29 +02:00
Lee Brown 227af02f31 issue#16 web-app account signup
Account signup works with validation and translations.
2019-07-31 13:47:30 -08:00

260 lines
8.1 KiB
Go

package web
import (
"context"
"encoding/json"
"fmt"
"geeks-accelerator/oss/saas-starter-kit/internal/platform/web/webcontext"
"html/template"
"net/http"
"os"
"path"
"path/filepath"
"strings"
"geeks-accelerator/oss/saas-starter-kit/internal/platform/web/weberror"
)
const (
charsetUTF8 = "charset=UTF-8"
)
// MIME types
const (
MIMEApplicationJSON = "application/json"
MIMEApplicationJSONCharsetUTF8 = MIMEApplicationJSON + "; " + charsetUTF8
MIMETextHTML = "text/html"
MIMETextHTMLCharsetUTF8 = MIMETextHTML + "; " + charsetUTF8
MIMETextPlain = "text/plain"
MIMETextPlainCharsetUTF8 = MIMETextPlain + "; " + charsetUTF8
MIMEOctetStream = "application/octet-stream"
)
// RespondJsonError sends an error formatted as JSON response back to the client.
func RespondJsonError(ctx context.Context, w http.ResponseWriter, err error) error {
// Set the status code for the request logger middleware.
// If the context is missing this value, request the service
// to be shutdown gracefully.
v, err := webcontext.ContextValues(ctx)
if err != nil {
return err
}
// If the error was of the type *Error, the handler has
// a specific status code and error to return.
webErr := weberror.NewError(ctx, err, v.StatusCode).(*weberror.Error)
v.StatusCode = webErr.Status
return RespondJson(ctx, w, webErr.Display(ctx), webErr.Status)
}
// RespondJson converts a Go value to JSON and sends it to the client.
// If code is StatusNoContent, v is expected to be nil.
func RespondJson(ctx context.Context, w http.ResponseWriter, data interface{}, statusCode int) error {
// Set the status code for the request logger middleware.
// If the context is missing this value, request the service
// to be shutdown gracefully.
v, err := webcontext.ContextValues(ctx)
if err != nil {
return err
}
v.StatusCode = statusCode
// If there is nothing to marshal then set status code and return.
if statusCode == http.StatusNoContent {
w.WriteHeader(statusCode)
return nil
}
// Check to see if the json has already been encoded.
jsonData, ok := data.([]byte)
if !ok {
// Convert the response value to JSON.
var err error
jsonData, err = json.Marshal(data)
if err != nil {
return err
}
}
// Set the content type and headers once we know marshaling has succeeded.
w.Header().Set("Content-Type", MIMEApplicationJSONCharsetUTF8)
// Write the status code to the response.
w.WriteHeader(statusCode)
// Send the result back to the client.
if _, err := w.Write(jsonData); err != nil {
return err
}
return nil
}
// RespondError sends an error back to the client as plain text with
// the status code 500 Internal Service Error
func RespondError(ctx context.Context, w http.ResponseWriter, er error) error {
return RespondErrorStatus(ctx, w, er, 0)
}
// RespondErrorStatus sends an error back to the client as plain text with
// the specified HTTP status code.
func RespondErrorStatus(ctx context.Context, w http.ResponseWriter, er error, statusCode int) error {
// Set the status code for the request logger middleware.
// If the context is missing this value, request the service
// to be shutdown gracefully.
v, err := webcontext.ContextValues(ctx)
if err != nil {
return err
}
// If the error was of the type *Error, the handler has
// a specific status code and error to return.
webErr := weberror.NewError(ctx, er, v.StatusCode).(*weberror.Error)
v.StatusCode = webErr.Status
respErr := webErr.Display(ctx).String()
switch webcontext.ContextEnv(ctx) {
case webcontext.Env_Dev, webcontext.Env_Stage:
respErr = respErr + fmt.Sprintf("\n%s\n%+v", webErr.Error(), webErr.Cause)
}
return RespondText(ctx, w, respErr, statusCode)
}
// RespondText sends text back to the client as plain text with the specified HTTP status code.
func RespondText(ctx context.Context, w http.ResponseWriter, text string, statusCode int) error {
return Respond(ctx, w, []byte(text), statusCode, MIMETextPlainCharsetUTF8)
}
// Respond writes the data to the client with the specified HTTP status code and
// content type.
func Respond(ctx context.Context, w http.ResponseWriter, data []byte, statusCode int, contentType string) error {
// Set the status code for the request logger middleware.
// If the context is missing this value, request the service
// to be shutdown gracefully.
v, err := webcontext.ContextValues(ctx)
if err != nil {
return err
}
v.StatusCode = statusCode
// If there is nothing to marshal then set status code and return.
if statusCode == http.StatusNoContent {
w.WriteHeader(statusCode)
return nil
}
// Set the content type and headers once we know marshaling has succeeded.
w.Header().Set("Content-Type", contentType)
// Write the status code to the response.
w.WriteHeader(statusCode)
// Send the result back to the client.
if _, err := w.Write(data); err != nil {
return err
}
return nil
}
// RenderError sends an error back to the client as html with
// the specified HTTP status code.
func RenderError(ctx context.Context, w http.ResponseWriter, r *http.Request, er error, renderer Renderer, templateLayoutName, templateContentName, contentType string) error {
// Set the status code for the request logger middleware.
// If the context is missing this value, request the service
// to be shutdown gracefully.
v, err := webcontext.ContextValues(ctx)
if err != nil {
return err
}
// If the error was of the type *Error, the handler has
// a specific status code and error to return.
webErr := weberror.NewError(ctx, er, v.StatusCode).(*weberror.Error)
v.StatusCode = webErr.Status
respErr := webErr.Display(ctx)
var fullError string
switch webcontext.ContextEnv(ctx) {
case webcontext.Env_Dev, webcontext.Env_Stage:
if webErr.Cause != nil && webErr.Cause.Error() != webErr.Err.Error() {
fullError = fmt.Sprintf("\n%s\n%+v", webErr.Error(), webErr.Cause)
} else {
fullError = fmt.Sprintf("%+v", webErr.Err)
}
fullError = strings.Replace(fullError, "\n", "<br/>", -1)
}
data := map[string]interface{}{
"statusCode": webErr.Status,
"errorMessage": respErr.Error,
"fullError": template.HTML(fullError),
}
return renderer.Render(ctx, w, r, templateLayoutName, templateContentName, contentType, webErr.Status, data)
}
// Static registers a new route with path prefix to serve static files from the
// provided root directory. All errors will result in 404 File Not Found.
func Static(rootDir, prefix string) Handler {
h := func(ctx context.Context, w http.ResponseWriter, r *http.Request, params map[string]string) error {
err := StaticHandler(ctx, w, r, params, rootDir, prefix)
if err != nil {
return RespondErrorStatus(ctx, w, err, http.StatusNotFound)
}
return nil
}
return h
}
// StaticHandler sends a static file wo the client. The error is returned directly
// from this function allowing it to be wrapped by a Handler. The handler then was the
// the ability to format/display the error before responding to the client.
func StaticHandler(ctx context.Context, w http.ResponseWriter, r *http.Request, params map[string]string, rootDir, prefix string) error {
// Parse the URL from the http request.
urlPath := path.Clean("/" + r.URL.Path) // "/"+ for security
urlPath = strings.TrimLeft(urlPath, "/")
// Remove the static directory name from the url
rootDirName := filepath.Base(rootDir)
if strings.HasPrefix(urlPath, rootDirName) {
urlPath = strings.Replace(urlPath, rootDirName, "", 1)
}
// Also remove the URL prefix used to serve the static file since
// this does not need to match any existing directory structure.
if prefix != "" {
urlPath = strings.TrimLeft(urlPath, prefix)
}
// Resolve the root directory to an absolute path
sd, err := filepath.Abs(rootDir)
if err != nil {
return err
}
// Append the requested file to the root directory
filePath := filepath.Join(sd, urlPath)
// Make sure the file exists before attempting to serve it so
// have the opportunity to handle the when a file does not exist.
if _, err := os.Stat(filePath); err != nil {
return err
}
// Serve the file from the local file system.
http.ServeFile(w, r, filePath)
return nil
}