oef/renderer/renderer.go

371 lines
8.8 KiB
Go

package renderer
import (
"bufio"
"encoding/json"
"fmt"
"html/template"
"io"
"log"
"mime"
"net/http"
"net/url"
"os"
"path"
"path/filepath"
"reflect"
"strings"
"time"
"git.andreafazzi.eu/andrea/oef/errors"
"github.com/gocarina/gocsv"
"github.com/gorilla/schema"
"github.com/gorilla/sessions"
jwt "github.com/dgrijalva/jwt-go"
)
type Renderer interface {
Render(http.ResponseWriter, *http.Request, *sessions.CookieStore, interface{}, ...url.Values) error
}
type JSONRenderer struct{}
type CSVRenderer struct{}
type PDFRenderer struct{}
type htmlTemplateData struct {
Data interface{}
Options url.Values
Claims jwt.MapClaims
FlashMessages []interface{}
}
type JsonResponse struct {
Result []byte
Error []byte
}
var (
// REMOVE
// store = sessions.NewCookieStore([]byte(config.Config.Keys.CookieStoreKey))
// currRenderer Renderer
// Render map[string]func(http.ResponseWriter, *http.Request, *sessions.CookieStore, interface{}, ...url.Values)
contentTypeToFormat = map[string]string{
"application/x-www-form-urlencoded": "html",
"text/html; charset=utf-8": "html",
"application/json": "json",
"text/csv; charset=utf-8": "csv",
"application/pdf": "pdf",
}
)
func init() {
//REMOVE
// htmlRenderer, err := NewHTMLRenderer("templates/")
// if err != nil {
// panic(err)
// }
// jsonRenderer, err := NewJSONRenderer()
// if err != nil {
// panic(err)
// }
// csvRenderer, err := NewCSVRenderer()
// if err != nil {
// panic(err)
// }
// pdfRenderer, err := NewPDFRenderer()
// if err != nil {
// panic(err)
// }
// Render = make(map[string]func(http.ResponseWriter, *http.Request, *sessions.CookieStore, interface{}, ...url.Values))
// Render["html"] = func(w http.ResponseWriter, r *http.Request, store *sessions.CookieStore, data interface{}, options ...url.Values) {
// htmlRenderer.Render(w, r, store, data, options...)
// }
// Render["json"] = func(w http.ResponseWriter, r *http.Request, store *sessions.CookieStore, data interface{}, options ...url.Values) {
// jsonRenderer.Render(w, r, store, data, options...)
// }
// Render["csv"] = func(w http.ResponseWriter, r *http.Request, store *sessions.CookieStore, data interface{}, options ...url.Values) {
// csvRenderer.Render(w, r, data, options...)
// }
// Render["pdf"] = func(w http.ResponseWriter, r *http.Request, store *sessions.CookieStore, data interface{}, options ...url.Values) {
// pdfRenderer.Render(w, r, data, options...)
// }
}
func Query(values ...string) template.URL {
return query(values...)
}
// FIXME: Not safe. We should check if it responds to the error interface instead.
func isErrorType(data interface{}) bool {
return strings.Contains(strings.ToLower(reflect.TypeOf(data).String()), "error")
}
func NewJSONRenderer() (*JSONRenderer, error) {
return &JSONRenderer{}, nil
}
func (rend *JSONRenderer) Render(w http.ResponseWriter, r *http.Request, store *sessions.CookieStore, data interface{}, options ...url.Values) error {
w.Header().Set("Content-Type", "application/json; charset=utf-8")
if isErrorType(data) {
j, err := json.Marshal(JsonResponse{nil, []byte(data.(error).Error())})
if err != nil {
return err
}
w.WriteHeader(http.StatusInternalServerError)
w.Write(j)
} else {
result, err := json.Marshal(data)
if err != nil {
return err
}
response, err := json.Marshal(JsonResponse{result, nil})
if err != nil {
return err
}
w.Write(response)
}
return nil
}
func NewCSVRenderer() (*CSVRenderer, error) {
return &CSVRenderer{}, nil
}
func (rend *CSVRenderer) Render(w http.ResponseWriter, r *http.Request, data interface{}, options ...url.Values) error {
w.Header().Set("Content-Type", "text/csv; charset=utf-8")
if isErrorType(data) {
j, err := json.Marshal(JsonResponse{nil, []byte(data.(error).Error())})
if err != nil {
return err
}
w.WriteHeader(http.StatusInternalServerError)
w.Write(j)
} else {
result, err := gocsv.MarshalString(data)
if err != nil {
return err
}
response, err := json.Marshal(JsonResponse{[]byte(result), nil})
if err != nil {
return err
}
w.Write(response)
}
return nil
}
func NewPDFRenderer() (*PDFRenderer, error) {
return &PDFRenderer{}, nil
}
func (rend *PDFRenderer) Render(w http.ResponseWriter, r *http.Request, data interface{}, options ...url.Values) error {
fileInfo := data.(map[string]string)
filename := fileInfo["filename"]
w.Header().Set("Content-Type", "application/pdf")
w.Header().Set("Content-Disposition", fmt.Sprintf("attachment; filename=\"%s\"", filepath.Base(filename)))
f, err := os.Open(filename)
if err != nil {
panic(err)
}
reader := bufio.NewReader(f)
io.Copy(w, reader)
return nil
}
type HTMLRenderer struct {
TemplatePath string
templates map[string]*template.Template
}
func NewHTMLRenderer(templatePath string) (*HTMLRenderer, error) {
r := &HTMLRenderer{
TemplatePath: templatePath,
templates: make(map[string]*template.Template),
}
fns, err := filepath.Glob(filepath.Join(templatePath, "*.tpl"))
if err != nil {
panic(err)
}
lfns, err := filepath.Glob(filepath.Join(templatePath, "layout", "*.tpl"))
if err != nil {
panic(err)
}
for _, fn := range fns {
tplName := filepath.Base(fn)
tplName = strings.TrimSuffix(tplName, path.Ext(tplName))
tplName = strings.TrimSuffix(tplName, path.Ext(tplName))
files := append(lfns, fn)
if err != nil {
return nil, err
}
r.templates[tplName] = template.New(tplName)
r.templates[tplName] = template.Must(r.templates[tplName].Funcs(funcMap).ParseFiles(files...))
}
return r, nil
}
// REMOVE
// func Use(r Renderer) {
// currRenderer = r
// }
func (rend *HTMLRenderer) writeError(w http.ResponseWriter, r *http.Request, data interface{}) {
var t *template.Template
err, ok := data.(*htmlTemplateData).Data.(*errors.Error)
if ok {
t, ok = rend.templates[err.TemplateName]
if !ok {
panic(fmt.Errorf("Error template not found! Can't proceed, sorry."))
}
} else {
t, ok = rend.templates["error"]
if !ok {
panic(fmt.Errorf("Error template not found! Can't proceed, sorry."))
}
log.Println(data.(*htmlTemplateData).Data.(error))
}
w.Header().Set("Content-Type", "text/html; charset=utf-8")
e := t.ExecuteTemplate(w, "base", data)
if e != nil {
panic(e)
}
}
func (rend *HTMLRenderer) Render(w http.ResponseWriter, r *http.Request, store *sessions.CookieStore, data interface{}, options ...url.Values) error {
var claims jwt.MapClaims
if r.Context().Value("user") != nil {
claims = r.Context().Value("user").(*jwt.Token).Claims.(jwt.MapClaims)
}
if data != nil && isErrorType(data) {
session, err := store.Get(r, "flash-session")
if err != nil {
rend.writeError(w, r, &htmlTemplateData{err, nil, claims, nil})
return err
}
fm := session.Flashes()
err = session.Save(r, w)
if err != nil {
rend.writeError(w, r, &htmlTemplateData{err, nil, claims, fm})
return err
}
rend.writeError(w, r, &htmlTemplateData{data.(error), nil, claims, fm})
return err
} else {
t, ok := rend.templates[options[0]["tpl_content"][0]]
if !ok {
err := fmt.Errorf("Template %s not found", options[0]["tpl_content"][0])
rend.writeError(w, r, &htmlTemplateData{err, nil, claims, nil})
return err
}
w.Header().Set("Content-Type", "text/html; charset=utf-8")
session, err := store.Get(r, "flash-session")
if err != nil {
rend.writeError(w, r, &htmlTemplateData{err, nil, claims, nil})
return err
}
fm := session.Flashes()
err = session.Save(r, w)
if err != nil {
rend.writeError(w, r, &htmlTemplateData{err, nil, claims, fm})
return err
}
err = t.ExecuteTemplate(w, options[0]["tpl_layout"][0], &htmlTemplateData{data, options[0], claims, fm})
if err != nil {
rend.writeError(w, r, &htmlTemplateData{err, nil, claims, fm})
return err
}
}
return nil
}
func GetContentFormat(r *http.Request) string {
var (
t string
err error
)
contentType := r.Header.Get("Content-type")
if contentType == "" {
return "application/octet-stream"
}
t, _, err = mime.ParseMediaType(contentType)
if err != nil {
return ""
}
return contentTypeToFormat[t]
}
func Decode(dst interface{}, r *http.Request) error {
switch GetContentFormat(r) {
case "html":
var timeConverter = func(value string) reflect.Value {
if value == "" {
return reflect.ValueOf(time.Time{})
}
if v, err := time.Parse(time.RFC3339, value); err == nil {
return reflect.ValueOf(v)
} else {
log.Println(value, err)
}
return reflect.Value{}
}
if err := r.ParseForm(); err != nil {
return err
}
decoder := schema.NewDecoder()
decoder.RegisterConverter(time.Time{}, timeConverter)
if err := decoder.Decode(dst, r.PostForm); err != nil {
return err
}
case "json":
decoder := json.NewDecoder(r.Body)
err := decoder.Decode(&dst)
if err != nil {
return err
}
}
return nil
}