frontend: add home template

Signed-off-by: Ahmet Alp Balkan <ahmetb@google.com>
This commit is contained in:
Ahmet Alp Balkan 2018-06-24 23:28:44 -07:00
parent 11ba676828
commit 3cbda5fc65
4 changed files with 167 additions and 93 deletions

109
src/frontend/handlers.go Normal file
View file

@ -0,0 +1,109 @@
package main
import (
"context"
"html/template"
"log"
"net/http"
"github.com/google/uuid"
)
var (
homeTemplate = template.Must(template.ParseFiles("templates/home.html"))
)
func refreshCookies(next http.HandlerFunc) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
for _, c := range r.Cookies() {
c.MaxAge = cookieMaxAge
http.SetCookie(w, c)
}
next(w, r)
}
}
func ensureSessionID(next http.HandlerFunc) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
var sessionID string
c, err := r.Cookie(cookieSessionID)
if err == http.ErrNoCookie {
u, _ := uuid.NewRandom()
sessionID = u.String()
http.SetCookie(w, &http.Cookie{
Name: cookieSessionID,
Value: sessionID,
MaxAge: cookieMaxAge,
})
} else if err != nil {
log.Printf("unrecognized cookie error: %+v", err)
w.WriteHeader(http.StatusInternalServerError)
return
} else {
sessionID = c.Value
}
ctx := context.WithValue(r.Context(), ctxKeySessionID{}, sessionID)
r = r.WithContext(ctx)
next(w, r)
}
}
func (fe *frontendServer) homeHandler(w http.ResponseWriter, r *http.Request) {
log.Printf("[home] session_id=%+v", r.Context().Value(ctxKeySessionID{}))
currencies, err := fe.getCurrencies(r.Context())
if err != nil {
log.Println(err) // TODO(ahmetb) use structured logging
w.WriteHeader(http.StatusInternalServerError)
return
}
log.Printf("currencies: %+v", currencies)
products, err := fe.getProducts(r.Context())
if err != nil {
log.Println(err)
w.WriteHeader(http.StatusInternalServerError)
return
}
log.Printf("# products: %d", len(products))
if err := homeTemplate.Execute(w, map[string]interface{}{
"user_currency": currentCurrency(r),
"currencies": currencies,
"products": products,
}); err != nil {
log.Println(err)
}
}
func (fe *frontendServer) logoutHandler(w http.ResponseWriter, r *http.Request) {
log.Printf("[home] session_id=%+v", r.Context().Value(ctxKeySessionID{}))
// clear all cookies
for _, c := range r.Cookies() {
c.MaxAge = -1
http.SetCookie(w, c)
}
w.Header().Set("Location", "/")
w.WriteHeader(http.StatusFound)
}
func (fe *frontendServer) setCurrencyHandler(w http.ResponseWriter, r *http.Request) {
log.Printf("[setCurrency] session_id=%+v", r.Context().Value(ctxKeySessionID{}))
cur := r.FormValue("currency_code")
if cur != "" {
http.SetCookie(w, &http.Cookie{
Name: cookieCurrency,
Value: cur,
MaxAge: cookieMaxAge,
})
}
w.Header().Set("Location", "/")
w.WriteHeader(http.StatusFound)
}
func currentCurrency(r *http.Request) string {
c, _ := r.Cookie(cookieCurrency)
if c != nil {
return c.Value
}
return defaultCurrency
}

View file

@ -1,4 +1,4 @@
package main // import "frontend"
package main
import (
"context"
@ -7,11 +7,8 @@ import (
"net/http"
"os"
"github.com/google/uuid"
"github.com/gorilla/mux"
"google.golang.org/grpc"
pb "frontend/genproto"
)
const (
@ -64,103 +61,20 @@ func main() {
}
r := mux.NewRouter()
r.HandleFunc("/", svc.ensureSessionID(svc.homeHandler)).
r.HandleFunc("/", refreshCookies(
ensureSessionID(
svc.homeHandler))).
Methods(http.MethodGet)
r.HandleFunc("/logout", svc.logoutHandler).
Methods(http.MethodGet)
r.HandleFunc("/setCurrency", svc.ensureSessionID(svc.setCurrencyHandler)).
r.HandleFunc("/setCurrency", refreshCookies(
ensureSessionID(
svc.setCurrencyHandler))).
Methods(http.MethodPost)
log.Printf("starting server on :" + srvPort)
log.Fatal(http.ListenAndServe("localhost:"+srvPort, r))
}
func (fe *frontendServer) ensureSessionID(next http.HandlerFunc) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
var sessionID string
c, err := r.Cookie(cookieSessionID)
if err == http.ErrNoCookie {
u, _ := uuid.NewRandom()
sessionID = u.String()
} else if err != nil {
log.Printf("unrecognized cookie error: %+v", err)
w.WriteHeader(http.StatusInternalServerError)
return
} else {
sessionID = c.Value
}
http.SetCookie(w, &http.Cookie{
Name: cookieSessionID,
Value: sessionID,
MaxAge: cookieMaxAge,
})
ctx := context.WithValue(r.Context(), ctxKeySessionID{}, sessionID)
r = r.WithContext(ctx)
next(w, r)
}
}
func (fe *frontendServer) homeHandler(w http.ResponseWriter, r *http.Request) {
log.Printf("[home] session_id=%+v", r.Context().Value(ctxKeySessionID{}))
currencies, err := fe.getCurrencies(r.Context())
if err != nil {
log.Println(err) // TODO(ahmetb) use structured logging
w.WriteHeader(http.StatusInternalServerError)
return
}
log.Printf("currencies: %+v", currencies)
products, err := fe.getProducts(r.Context())
if err != nil {
log.Println(err)
w.WriteHeader(http.StatusInternalServerError)
}
log.Printf("# products: %d", len(products))
}
func (fe *frontendServer) logoutHandler(w http.ResponseWriter, r *http.Request) {
log.Printf("[home] session_id=%+v", r.Context().Value(ctxKeySessionID{}))
// clear all cookies
for _, c := range r.Cookies() {
c.MaxAge = -1
http.SetCookie(w, c)
}
w.Header().Set("Location", "/")
w.WriteHeader(http.StatusFound)
}
func (fe *frontendServer) setCurrencyHandler(w http.ResponseWriter, r *http.Request) {
log.Printf("[setCurrency] session_id=%+v", r.Context().Value(ctxKeySessionID{}))
cur := r.FormValue("currency_code")
if cur != "" {
http.SetCookie(w, &http.Cookie{
Name: cookieCurrency,
Value: cur,
MaxAge: cookieMaxAge,
})
}
w.Header().Set("Location", "/")
w.WriteHeader(http.StatusFound)
}
func (fe *frontendServer) getCurrencies(ctx context.Context) ([]string, error) {
currs, err := pb.NewCurrencyServiceClient(fe.currencySvcConn).GetSupportedCurrencies(ctx, &pb.Empty{})
if err != nil {
return nil, err
}
var out []string
for _, c := range currs.CurrencyCodes {
if _, ok := whitelistedCurrencies[c]; ok {
out = append(out, c)
}
}
return out, nil
}
func (fe *frontendServer) getProducts(ctx context.Context) ([]*pb.Product, error) {
resp, err := pb.NewProductCatalogServiceClient(fe.productCatalogSvcConn).ListProducts(ctx, &pb.Empty{})
return resp.GetProducts(), err
}
func mustMapEnv(target *string, envKey string) {
v := os.Getenv(envKey)
if v == "" {

28
src/frontend/rpc.go Normal file
View file

@ -0,0 +1,28 @@
package main
import (
"context"
pb "frontend/genproto"
)
func (fe *frontendServer) getCurrencies(ctx context.Context) ([]string, error) {
currs, err := pb.NewCurrencyServiceClient(fe.currencySvcConn).
GetSupportedCurrencies(ctx, &pb.Empty{})
if err != nil {
return nil, err
}
var out []string
for _, c := range currs.CurrencyCodes {
if _, ok := whitelistedCurrencies[c]; ok {
out = append(out, c)
}
}
return out, nil
}
func (fe *frontendServer) getProducts(ctx context.Context) ([]*pb.Product, error) {
resp, err := pb.NewProductCatalogServiceClient(fe.productCatalogSvcConn).
ListProducts(ctx, &pb.Empty{})
return resp.GetProducts(), err
}

View file

@ -0,0 +1,23 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Hipster Shop</title>
</head>
<body>
<form method="post" action="/setCurrency" id="currencySelector">
<select name="currency_code"
onchange="document.getElementById('currencySelector').submit()">
{{ range $.currencies }}
<option {{ if (eq . $.user_currency)}}selected="selected"{{end}}>{{.}}</option>
{{ end}}
</select>
</form>
<p>{{ len $.products }} products</p>
</body>
</html>