frontend: add home template
Signed-off-by: Ahmet Alp Balkan <ahmetb@google.com>
This commit is contained in:
parent
11ba676828
commit
3cbda5fc65
4 changed files with 167 additions and 93 deletions
109
src/frontend/handlers.go
Normal file
109
src/frontend/handlers.go
Normal 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
|
||||||
|
}
|
|
@ -1,4 +1,4 @@
|
||||||
package main // import "frontend"
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
@ -7,11 +7,8 @@ import (
|
||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
|
|
||||||
"github.com/google/uuid"
|
|
||||||
"github.com/gorilla/mux"
|
"github.com/gorilla/mux"
|
||||||
"google.golang.org/grpc"
|
"google.golang.org/grpc"
|
||||||
|
|
||||||
pb "frontend/genproto"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
@ -64,103 +61,20 @@ func main() {
|
||||||
}
|
}
|
||||||
|
|
||||||
r := mux.NewRouter()
|
r := mux.NewRouter()
|
||||||
r.HandleFunc("/", svc.ensureSessionID(svc.homeHandler)).
|
r.HandleFunc("/", refreshCookies(
|
||||||
|
ensureSessionID(
|
||||||
|
svc.homeHandler))).
|
||||||
Methods(http.MethodGet)
|
Methods(http.MethodGet)
|
||||||
r.HandleFunc("/logout", svc.logoutHandler).
|
r.HandleFunc("/logout", svc.logoutHandler).
|
||||||
Methods(http.MethodGet)
|
Methods(http.MethodGet)
|
||||||
r.HandleFunc("/setCurrency", svc.ensureSessionID(svc.setCurrencyHandler)).
|
r.HandleFunc("/setCurrency", refreshCookies(
|
||||||
|
ensureSessionID(
|
||||||
|
svc.setCurrencyHandler))).
|
||||||
Methods(http.MethodPost)
|
Methods(http.MethodPost)
|
||||||
log.Printf("starting server on :" + srvPort)
|
log.Printf("starting server on :" + srvPort)
|
||||||
log.Fatal(http.ListenAndServe("localhost:"+srvPort, r))
|
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) {
|
func mustMapEnv(target *string, envKey string) {
|
||||||
v := os.Getenv(envKey)
|
v := os.Getenv(envKey)
|
||||||
if v == "" {
|
if v == "" {
|
||||||
|
|
28
src/frontend/rpc.go
Normal file
28
src/frontend/rpc.go
Normal 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
|
||||||
|
}
|
23
src/frontend/templates/home.html
Normal file
23
src/frontend/templates/home.html
Normal 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>
|
Loading…
Reference in a new issue