oef/renderer/renderer.go
2020-02-19 14:57:31 +01:00

316 lines
7.1 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"
oef_errors "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
WriteError(http.ResponseWriter, *http.Request, interface{})
}
type JSONRenderer struct{}
type CSVRenderer struct{}
type PDFRenderer struct{}
type htmlTemplateData struct {
Data interface{}
Options url.Values
Claims jwt.MapClaims
}
type JsonResponse struct {
Result []byte
Error []byte
}
var (
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 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) WriteError(w http.ResponseWriter, r *http.Request, data interface{}) {
}
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
}
func (rend *HTMLRenderer) writeError(w http.ResponseWriter, r *http.Request, data interface{}) {
var (
t *template.Template
claims jwt.MapClaims
)
if r.Context().Value("user") != nil {
claims = r.Context().Value("user").(*jwt.Token).Claims.(jwt.MapClaims)
}
err, ok := data.(*oef_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."))
}
}
w.Header().Set("Content-Type", "text/html; charset=utf-8")
if claims != nil {
e := t.ExecuteTemplate(w, "base", &htmlTemplateData{data, nil, claims})
if e != nil {
panic(e)
}
} else {
e := t.ExecuteTemplate(w, "error", &htmlTemplateData{data, nil, nil})
if e != nil {
panic(e)
}
}
}
func (rend *HTMLRenderer) WriteError(w http.ResponseWriter, r *http.Request, data interface{}) {
rend.writeError(w, r, data)
}
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 err, ok := data.(*oef_errors.Error); ok {
// rend.writeError(w, r, &htmlTemplateData{data, nil, claims})
return err
}
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})
return err
}
w.Header().Set("Content-Type", "text/html; charset=utf-8")
if err := t.ExecuteTemplate(w, options[0]["tpl_layout"][0], &htmlTemplateData{data, options[0], claims}); err != nil {
// rend.writeError(w, r, &htmlTemplateData{err, nil, claims})
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
}