347 lines
8 KiB
Go
347 lines
8 KiB
Go
package renderer
|
|
|
|
import (
|
|
"bufio"
|
|
"encoding/json"
|
|
"fmt"
|
|
"html/template"
|
|
"io"
|
|
"log"
|
|
|
|
"git.andreafazzi.eu/andrea/oef/config"
|
|
"github.com/gorilla/sessions"
|
|
"mime"
|
|
"net/http"
|
|
"net/url"
|
|
"os"
|
|
"path"
|
|
"path/filepath"
|
|
"reflect"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/gocarina/gocsv"
|
|
"github.com/gorilla/schema"
|
|
|
|
jwt "github.com/dgrijalva/jwt-go"
|
|
)
|
|
|
|
type Renderer interface {
|
|
Render(http.ResponseWriter, *http.Request, 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 (
|
|
store = sessions.NewCookieStore([]byte(config.Config.Keys.CookieStoreKey))
|
|
currRenderer Renderer
|
|
Render map[string]func(http.ResponseWriter, *http.Request, interface{}, ...url.Values)
|
|
|
|
contentTypeToFormat = map[string]string{
|
|
"application/x-www-form-urlencoded": "html",
|
|
"application/json": "json",
|
|
"text/csv; charset=utf-8": "csv",
|
|
"application/pdf": "pdf",
|
|
}
|
|
)
|
|
|
|
func init() {
|
|
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, interface{}, ...url.Values))
|
|
|
|
Render["html"] = func(w http.ResponseWriter, r *http.Request, data interface{}, options ...url.Values) {
|
|
htmlRenderer.Render(w, r, data, options...)
|
|
}
|
|
|
|
Render["json"] = func(w http.ResponseWriter, r *http.Request, data interface{}, options ...url.Values) {
|
|
jsonRenderer.Render(w, r, data, options...)
|
|
}
|
|
|
|
Render["csv"] = func(w http.ResponseWriter, r *http.Request, data interface{}, options ...url.Values) {
|
|
csvRenderer.Render(w, r, data, options...)
|
|
}
|
|
|
|
Render["pdf"] = func(w http.ResponseWriter, r *http.Request, 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, 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 Use(r Renderer) {
|
|
currRenderer = r
|
|
}
|
|
|
|
func (rend *HTMLRenderer) writeError(w http.ResponseWriter, r *http.Request, data interface{}) {
|
|
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, data interface{}, options ...url.Values) {
|
|
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})
|
|
}
|
|
fm := session.Flashes()
|
|
err = session.Save(r, w)
|
|
if err != nil {
|
|
rend.writeError(w, r, &htmlTemplateData{err, nil, claims, fm})
|
|
}
|
|
rend.writeError(w, r, &htmlTemplateData{data.(error), nil, claims, fm})
|
|
} 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})
|
|
}
|
|
|
|
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})
|
|
}
|
|
fm := session.Flashes()
|
|
err = session.Save(r, w)
|
|
if err != nil {
|
|
rend.writeError(w, r, &htmlTemplateData{err, nil, claims, fm})
|
|
}
|
|
|
|
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})
|
|
}
|
|
}
|
|
}
|
|
|
|
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
|
|
}
|