2019-11-04 15:00:46 +01:00
|
|
|
package renderer
|
|
|
|
|
|
|
|
import (
|
|
|
|
"fmt"
|
|
|
|
"html/template"
|
2020-01-28 08:58:00 +01:00
|
|
|
"io/ioutil"
|
2019-11-04 15:00:46 +01:00
|
|
|
"net/url"
|
2020-02-12 11:14:04 +01:00
|
|
|
"os"
|
2019-11-04 15:00:46 +01:00
|
|
|
"reflect"
|
2019-12-09 08:27:46 +01:00
|
|
|
"strconv"
|
2019-11-04 15:00:46 +01:00
|
|
|
"strings"
|
|
|
|
"time"
|
|
|
|
|
2020-02-19 09:15:50 +01:00
|
|
|
"git.andreafazzi.eu/andrea/oef/errors"
|
2019-12-07 08:58:30 +01:00
|
|
|
"git.andreafazzi.eu/andrea/oef/i18n"
|
2020-02-19 09:15:50 +01:00
|
|
|
|
2020-02-12 09:50:35 +01:00
|
|
|
"github.com/dchest/captcha"
|
2019-11-18 12:40:28 +01:00
|
|
|
jwt "github.com/dgrijalva/jwt-go"
|
2019-11-04 15:00:46 +01:00
|
|
|
"github.com/jinzhu/inflection"
|
|
|
|
yml "gopkg.in/yaml.v2"
|
2020-02-05 14:39:56 +01:00
|
|
|
|
|
|
|
"github.com/microcosm-cc/bluemonday"
|
|
|
|
"gopkg.in/russross/blackfriday.v2"
|
2019-11-04 15:00:46 +01:00
|
|
|
)
|
|
|
|
|
2019-11-13 15:54:17 +01:00
|
|
|
const (
|
|
|
|
MaxTextLength = 20
|
|
|
|
)
|
|
|
|
|
2019-11-04 15:00:46 +01:00
|
|
|
var (
|
|
|
|
funcMap = template.FuncMap{
|
2020-02-12 11:14:04 +01:00
|
|
|
"env": env,
|
2020-02-12 09:50:35 +01:00
|
|
|
"genCaptcha": genCaptcha,
|
2020-02-05 14:39:56 +01:00
|
|
|
"markdown": markdown,
|
2020-01-28 08:58:00 +01:00
|
|
|
"version": version,
|
2019-12-29 17:23:50 +01:00
|
|
|
"toInt": toInt,
|
2019-12-18 11:49:52 +01:00
|
|
|
"isResponseIn": isResponseIn,
|
2019-12-07 08:58:30 +01:00
|
|
|
"query": query,
|
|
|
|
"convertDate": convertDate,
|
|
|
|
"convertTime": convertTime,
|
|
|
|
"prettyDate": prettyDate,
|
|
|
|
"prettyTime": prettyTime,
|
|
|
|
"prettyDateTime": prettyDateTime,
|
2019-12-13 08:32:20 +01:00
|
|
|
"zeroTime": zeroTime,
|
2020-02-03 12:52:56 +01:00
|
|
|
"seconds": seconds,
|
2019-12-07 08:58:30 +01:00
|
|
|
"modelPath": modelPath,
|
|
|
|
"dict": dict,
|
|
|
|
"yaml": yaml,
|
|
|
|
"create": create,
|
|
|
|
"update": update,
|
|
|
|
"delete": delete,
|
|
|
|
"show": show,
|
|
|
|
"all": all,
|
|
|
|
"execute": execute,
|
|
|
|
"isSlice": isSlice,
|
|
|
|
"toSlice": toSlice,
|
|
|
|
"string": callString,
|
|
|
|
"incr": incr,
|
|
|
|
"mod2": mod2,
|
|
|
|
"toLower": toLower,
|
|
|
|
"anchor": anchor,
|
2020-02-03 12:52:56 +01:00
|
|
|
"alertLink": alertLink,
|
2019-12-07 08:58:30 +01:00
|
|
|
"html": html,
|
|
|
|
"field": field,
|
|
|
|
"modelName": modelName,
|
|
|
|
"active": active,
|
|
|
|
"pluralize": pluralize,
|
|
|
|
"lower": lower,
|
|
|
|
"trim": trim,
|
|
|
|
"username": username,
|
|
|
|
"isAdmin": isAdmin,
|
2020-02-05 14:39:56 +01:00
|
|
|
"isSchool": isSchool,
|
|
|
|
"modelId": modelId,
|
2019-12-07 08:58:30 +01:00
|
|
|
"isParticipant": isParticipant,
|
|
|
|
"isSubscriber": isSubscriber,
|
|
|
|
"attr": attr,
|
2019-11-04 15:00:46 +01:00
|
|
|
}
|
|
|
|
)
|
|
|
|
|
2020-02-12 11:14:04 +01:00
|
|
|
func env(key string) string {
|
|
|
|
return os.Getenv(key)
|
|
|
|
}
|
|
|
|
|
2020-02-12 09:50:35 +01:00
|
|
|
func genCaptcha() string {
|
|
|
|
return captcha.New()
|
|
|
|
}
|
|
|
|
|
2020-02-05 14:39:56 +01:00
|
|
|
func markdown(text string) string {
|
2020-02-14 13:54:13 +01:00
|
|
|
sanitized := strings.Replace(text, "\r\n", "\n", -1)
|
|
|
|
unsafe := blackfriday.Run([]byte(sanitized))
|
2020-02-05 14:39:56 +01:00
|
|
|
return string(bluemonday.UGCPolicy().SanitizeBytes(unsafe))
|
|
|
|
}
|
2020-01-28 08:58:00 +01:00
|
|
|
func version() string {
|
|
|
|
version, err := ioutil.ReadFile("./VERSION")
|
|
|
|
if err != nil {
|
|
|
|
return ""
|
|
|
|
}
|
2020-02-29 11:45:22 +01:00
|
|
|
return strings.TrimSpace(string(version))
|
2020-01-28 08:58:00 +01:00
|
|
|
}
|
|
|
|
|
2019-12-29 17:23:50 +01:00
|
|
|
func toInt(value float64) int {
|
|
|
|
return int(value)
|
|
|
|
}
|
|
|
|
|
2019-12-18 11:49:52 +01:00
|
|
|
func isResponseIn(id uint, answersIDs string) (bool, error) {
|
|
|
|
if answersIDs != "" {
|
|
|
|
ids := make([]uint, 0)
|
|
|
|
srIDs := strings.Split(answersIDs, " ")
|
|
|
|
for _, srID := range srIDs {
|
|
|
|
id, err := strconv.Atoi(srID)
|
|
|
|
if err != nil {
|
|
|
|
return false, err
|
|
|
|
}
|
|
|
|
ids = append(ids, uint(id))
|
|
|
|
}
|
|
|
|
|
|
|
|
for _, v := range ids {
|
|
|
|
if v == id {
|
|
|
|
return true, nil
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return false, nil
|
|
|
|
}
|
|
|
|
|
2019-11-18 12:40:28 +01:00
|
|
|
func username(claims jwt.MapClaims) string {
|
2019-12-02 10:48:46 +01:00
|
|
|
return claims["username"].(string)
|
2019-11-18 12:40:28 +01:00
|
|
|
}
|
|
|
|
|
2020-02-03 14:13:01 +01:00
|
|
|
func modelId(claims jwt.MapClaims) (uint, error) {
|
|
|
|
id, err := strconv.Atoi(claims["model_id"].(string))
|
2019-12-09 08:27:46 +01:00
|
|
|
if err != nil {
|
|
|
|
return 0, err
|
|
|
|
}
|
|
|
|
return uint(id), nil
|
|
|
|
}
|
|
|
|
|
2020-02-19 09:15:50 +01:00
|
|
|
func getRole(claims jwt.MapClaims) (string, error) {
|
|
|
|
role, ok := claims["role"]
|
|
|
|
if !ok {
|
|
|
|
return "", errors.NotAuthorized
|
|
|
|
}
|
|
|
|
return role.(string), nil
|
2019-11-18 12:40:28 +01:00
|
|
|
}
|
|
|
|
|
2020-02-19 09:15:50 +01:00
|
|
|
func isAdmin(claims jwt.MapClaims) (bool, error) {
|
|
|
|
role, err := getRole(claims)
|
|
|
|
if err != nil {
|
|
|
|
return false, err
|
|
|
|
}
|
|
|
|
return role == "administrator", nil
|
2019-11-22 11:16:27 +01:00
|
|
|
}
|
|
|
|
|
2020-02-19 09:15:50 +01:00
|
|
|
func isParticipant(claims jwt.MapClaims) (bool, error) {
|
|
|
|
role, err := getRole(claims)
|
|
|
|
if err != nil {
|
|
|
|
return false, err
|
|
|
|
}
|
|
|
|
|
|
|
|
return role == "participant", nil
|
2019-12-07 11:44:19 +01:00
|
|
|
}
|
|
|
|
|
2020-02-19 09:15:50 +01:00
|
|
|
func isSchool(claims jwt.MapClaims) (bool, error) {
|
|
|
|
role, err := getRole(claims)
|
|
|
|
if err != nil {
|
|
|
|
return false, err
|
|
|
|
}
|
|
|
|
|
|
|
|
return role == "school", nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func isSubscriber(claims jwt.MapClaims) (bool, error) {
|
|
|
|
role, err := getRole(claims)
|
|
|
|
if err != nil {
|
|
|
|
return false, err
|
|
|
|
}
|
|
|
|
|
|
|
|
return role == "subscriber", nil
|
2019-12-04 14:10:09 +01:00
|
|
|
}
|
|
|
|
|
2019-11-13 15:54:17 +01:00
|
|
|
func trim(text string) string {
|
|
|
|
if len(text) > MaxTextLength {
|
2019-11-15 10:41:32 +01:00
|
|
|
return text[0:MaxTextLength] + "…"
|
2019-11-13 15:54:17 +01:00
|
|
|
}
|
2019-11-15 10:41:32 +01:00
|
|
|
return text
|
2019-11-13 15:54:17 +01:00
|
|
|
}
|
|
|
|
|
2019-11-06 12:19:57 +01:00
|
|
|
func modelName(value interface{}) string {
|
2019-11-13 11:15:21 +01:00
|
|
|
t := reflect.TypeOf(value)
|
|
|
|
switch t.Kind() {
|
|
|
|
case reflect.Ptr:
|
2019-11-06 12:19:57 +01:00
|
|
|
return t.Elem().Name()
|
2019-11-13 11:15:21 +01:00
|
|
|
case reflect.Slice:
|
|
|
|
return strings.Replace(t.Elem().String(), "*orm.", "", -1)
|
|
|
|
default:
|
2019-11-06 12:19:57 +01:00
|
|
|
return t.Name()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func lower(text string) string {
|
|
|
|
return strings.ToLower(text)
|
|
|
|
}
|
|
|
|
|
|
|
|
func pluralize(text string) string {
|
|
|
|
return inflection.Plural(text)
|
|
|
|
}
|
|
|
|
|
2019-11-13 13:57:41 +01:00
|
|
|
func active(value string, options url.Values) string {
|
2019-12-09 09:45:45 +01:00
|
|
|
if len(options["tpl_content"]) > 0 {
|
|
|
|
model := strings.Title(inflection.Singular(strings.Split(options["tpl_content"][0], "_")[0]))
|
|
|
|
if value == model {
|
|
|
|
return "active"
|
|
|
|
}
|
2019-11-06 12:19:57 +01:00
|
|
|
}
|
|
|
|
return ""
|
|
|
|
}
|
2019-11-04 15:00:46 +01:00
|
|
|
func field(name string, value interface{}) interface{} {
|
|
|
|
if value != nil {
|
|
|
|
s := reflect.ValueOf(value).Elem()
|
|
|
|
return s.FieldByName(name).Interface()
|
|
|
|
} else {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func html(content string) template.HTML {
|
|
|
|
return template.HTML(content)
|
|
|
|
}
|
|
|
|
|
2019-12-06 10:59:00 +01:00
|
|
|
func attr(attr string) template.HTMLAttr {
|
|
|
|
return template.HTMLAttr(attr)
|
|
|
|
}
|
|
|
|
|
2019-11-04 15:00:46 +01:00
|
|
|
func anchor(text, url string) template.HTML {
|
|
|
|
return template.HTML(fmt.Sprintf("<a href=\"%s\">%s</a>", url, text))
|
|
|
|
}
|
|
|
|
|
2020-01-28 15:04:28 +01:00
|
|
|
func alertLink(text, url string) template.HTML {
|
|
|
|
return template.HTML(fmt.Sprintf("<a class=\"alert-link\" href=\"%s\">%s</a>", url, text))
|
|
|
|
}
|
|
|
|
|
2019-11-04 15:00:46 +01:00
|
|
|
func toLower(text string) string {
|
|
|
|
return strings.ToLower(text)
|
|
|
|
}
|
|
|
|
|
|
|
|
func mod2(value int) bool {
|
|
|
|
return value%2 == 0
|
|
|
|
}
|
|
|
|
|
|
|
|
func incr(value int) int {
|
|
|
|
return value + 1
|
|
|
|
}
|
|
|
|
|
|
|
|
func callString(value interface{}) string {
|
|
|
|
if value != nil {
|
2019-12-13 12:55:11 +01:00
|
|
|
switch reflect.ValueOf(value).Kind() {
|
|
|
|
case reflect.String:
|
2019-11-14 12:55:22 +01:00
|
|
|
return value.(string)
|
2019-12-13 12:55:11 +01:00
|
|
|
case reflect.Bool:
|
|
|
|
if value.(bool) {
|
|
|
|
return i18n.Text["answerCorrect"]["it"]
|
|
|
|
}
|
|
|
|
return "false"
|
|
|
|
default:
|
|
|
|
return reflect.ValueOf(value).MethodByName("String").Interface().(func() string)()
|
2019-11-14 12:55:22 +01:00
|
|
|
}
|
2019-11-04 15:00:46 +01:00
|
|
|
} else {
|
|
|
|
return ""
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func isSlice(value interface{}) bool {
|
|
|
|
return reflect.TypeOf(value).Kind() == reflect.Slice
|
|
|
|
}
|
|
|
|
|
|
|
|
func yaml(content string) (interface{}, error) {
|
|
|
|
var result interface{}
|
|
|
|
err := yml.Unmarshal([]byte(content), &result)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
return result, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func dict(values ...interface{}) (map[string]interface{}, error) {
|
|
|
|
if len(values)%2 != 0 {
|
2020-02-19 09:15:50 +01:00
|
|
|
return nil, fmt.Errorf("Invalid dict call, numbers of arguments is %d but should be a multiple of two", len(values))
|
2019-11-04 15:00:46 +01:00
|
|
|
}
|
|
|
|
dict := make(map[string]interface{}, len(values)/2)
|
|
|
|
for i := 0; i < len(values); i += 2 {
|
|
|
|
key, ok := values[i].(string)
|
|
|
|
if !ok {
|
2020-02-19 09:15:50 +01:00
|
|
|
return nil, fmt.Errorf("%s", "dict keys must be strings")
|
2019-11-04 15:00:46 +01:00
|
|
|
}
|
|
|
|
dict[key] = values[i+1]
|
|
|
|
}
|
|
|
|
return dict, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func toSlice(values ...string) interface{} {
|
|
|
|
var result []string
|
|
|
|
result = append(result, values...)
|
|
|
|
return result
|
|
|
|
}
|
|
|
|
|
|
|
|
func getType(myvar interface{}) (res string) {
|
|
|
|
t := reflect.TypeOf(myvar)
|
|
|
|
for t.Kind() == reflect.Ptr {
|
|
|
|
t = t.Elem()
|
|
|
|
res += "*"
|
|
|
|
}
|
|
|
|
return res + t.Name()
|
|
|
|
}
|
|
|
|
|
|
|
|
func query(values ...string) template.URL {
|
|
|
|
var (
|
|
|
|
urlValues url.Values
|
|
|
|
format bool
|
|
|
|
)
|
|
|
|
|
|
|
|
urlValues = make(url.Values)
|
|
|
|
|
|
|
|
for i := 0; i < len(values); i += 2 {
|
|
|
|
if values[i] == "format" {
|
|
|
|
format = true
|
|
|
|
}
|
|
|
|
urlValues.Add(values[i], values[i+1])
|
|
|
|
}
|
|
|
|
|
|
|
|
if !format {
|
|
|
|
urlValues.Set("format", "html")
|
|
|
|
}
|
|
|
|
|
|
|
|
return template.URL(urlValues.Encode())
|
|
|
|
}
|
|
|
|
|
|
|
|
func convertDate(value interface{}) string {
|
2019-12-07 08:58:30 +01:00
|
|
|
t, ok := value.(time.Time)
|
2019-11-04 15:00:46 +01:00
|
|
|
if !ok {
|
|
|
|
return ""
|
|
|
|
}
|
|
|
|
return fmt.Sprintf("%d-%02d-%02d", t.Year(), t.Month(), t.Day())
|
|
|
|
}
|
|
|
|
|
2019-11-14 12:55:22 +01:00
|
|
|
func prettyDate(value interface{}) string {
|
2019-12-07 08:58:30 +01:00
|
|
|
t, ok := value.(time.Time)
|
2019-11-14 12:55:22 +01:00
|
|
|
if !ok {
|
|
|
|
return ""
|
|
|
|
}
|
2019-12-13 12:55:11 +01:00
|
|
|
if zeroTime(&t) {
|
|
|
|
return i18n.Text["alwaysActiveContest"]["it"]
|
|
|
|
}
|
|
|
|
|
2019-11-14 12:55:22 +01:00
|
|
|
return fmt.Sprintf("%02d/%02d/%d", t.Day(), t.Month(), t.Year())
|
|
|
|
}
|
|
|
|
|
2019-12-07 08:58:30 +01:00
|
|
|
func prettyDateTime(value interface{}) string {
|
|
|
|
t, ok := value.(time.Time)
|
|
|
|
if !ok {
|
|
|
|
return ""
|
|
|
|
}
|
|
|
|
return fmt.Sprintf(i18n.Formats["dateTime"]["it"], t.Day(), t.Month(), t.Year(), t.Hour(), t.Minute())
|
|
|
|
}
|
|
|
|
|
2019-11-14 12:55:22 +01:00
|
|
|
func convertTime(value interface{}) string {
|
2019-12-07 08:58:30 +01:00
|
|
|
t, ok := value.(time.Time)
|
2019-11-14 12:55:22 +01:00
|
|
|
if !ok {
|
|
|
|
return ""
|
|
|
|
}
|
|
|
|
return fmt.Sprintf("%02d:%02d", t.Hour(), t.Minute())
|
|
|
|
}
|
|
|
|
|
2019-12-06 10:59:00 +01:00
|
|
|
func prettyTime(value interface{}) string {
|
|
|
|
return convertTime(value)
|
|
|
|
}
|
|
|
|
|
2019-12-13 08:32:20 +01:00
|
|
|
func zeroTime(t *time.Time) bool {
|
|
|
|
return *t == time.Time{}
|
|
|
|
}
|
|
|
|
|
2020-02-03 12:52:56 +01:00
|
|
|
func seconds(d time.Duration) int {
|
|
|
|
return int(d.Seconds())
|
2019-12-19 12:12:56 +01:00
|
|
|
}
|
|
|
|
|
2019-11-04 15:00:46 +01:00
|
|
|
func modelPath(model string, action string, id uint) string {
|
|
|
|
var q template.URL
|
|
|
|
|
|
|
|
action = strings.ToLower(action)
|
|
|
|
plural := inflection.Plural(strings.ToLower(model))
|
|
|
|
|
|
|
|
if action != "" {
|
|
|
|
switch action {
|
|
|
|
case "show":
|
|
|
|
q = query("tpl_layout", "base", "tpl_content", fmt.Sprintf("%s_%s", plural, action))
|
|
|
|
return fmt.Sprintf("/%s/%d?%s", plural, id, q)
|
|
|
|
case "update":
|
|
|
|
q = query("tpl_layout", "base", "tpl_content", fmt.Sprintf("%s_add_update", plural), "update", "true")
|
|
|
|
return fmt.Sprintf("/%s/%d/%s?%s", plural, id, action, q)
|
|
|
|
case "create":
|
|
|
|
q = query("tpl_layout", "base", "tpl_content", fmt.Sprintf("%s_add_update", plural))
|
|
|
|
return fmt.Sprintf("/%s/%s/?%s", plural, action, q)
|
|
|
|
case "delete":
|
|
|
|
q = query("tpl_layout", "base", "tpl_content", fmt.Sprintf("%s_%s", plural, action))
|
|
|
|
return fmt.Sprintf("/%s/%d/%s?%s", plural, id, action, q)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
q = query("tpl_layout", "base", "tpl_content", plural)
|
|
|
|
return fmt.Sprintf("/%s?%s", plural, q)
|
|
|
|
}
|
|
|
|
|
|
|
|
func all(model string) string {
|
|
|
|
plural := inflection.Plural(strings.ToLower(model))
|
|
|
|
q := query("tpl_layout", "base", "tpl_content", plural)
|
|
|
|
|
|
|
|
return fmt.Sprintf("/%s?%s", plural, q)
|
|
|
|
}
|
|
|
|
|
|
|
|
func create(model string) string {
|
|
|
|
action := "create"
|
|
|
|
plural := inflection.Plural(strings.ToLower(model))
|
|
|
|
q := query("tpl_layout", "base", "tpl_content", fmt.Sprintf("%s_add_update", plural))
|
|
|
|
|
|
|
|
return fmt.Sprintf("/%s/%s/?%s", plural, action, q)
|
|
|
|
}
|
|
|
|
|
|
|
|
func show(model string, id uint, format ...string) string {
|
|
|
|
action := "show"
|
|
|
|
plural := inflection.Plural(strings.ToLower(model))
|
|
|
|
|
|
|
|
args := []string{"tpl_layout", "base", "tpl_content", fmt.Sprintf("%s_%s", plural, action)}
|
|
|
|
|
|
|
|
if len(format) > 0 {
|
|
|
|
args = append(args, []string{"format", format[0]}...)
|
|
|
|
}
|
|
|
|
|
|
|
|
// q := query("tpl_layout", "base", "tpl_content", fmt.Sprintf("%s_%s", plural, action))
|
|
|
|
q := query(args...)
|
|
|
|
|
|
|
|
return fmt.Sprintf("/%s/%d?%s", plural, id, q)
|
|
|
|
}
|
|
|
|
|
|
|
|
func execute(model string, id uint) string {
|
|
|
|
action := "execute"
|
|
|
|
plural := inflection.Plural(strings.ToLower(model))
|
|
|
|
q := query("tpl_layout", "base", "tpl_content", fmt.Sprintf("%s_%s", plural, action))
|
|
|
|
|
|
|
|
return fmt.Sprintf("/%s/%d/%s?%s", plural, id, action, q)
|
|
|
|
}
|
|
|
|
|
|
|
|
func update(model string, id uint) string {
|
|
|
|
action := "update"
|
|
|
|
plural := inflection.Plural(strings.ToLower(model))
|
|
|
|
q := query("tpl_layout", "base", "tpl_content", fmt.Sprintf("%s_add_update", plural), "update", "true")
|
|
|
|
|
|
|
|
return fmt.Sprintf("/%s/%d/%s?%s", plural, id, action, q)
|
|
|
|
}
|
|
|
|
|
|
|
|
func delete(model string, id uint) string {
|
|
|
|
action := "delete"
|
|
|
|
plural := inflection.Plural(strings.ToLower(model))
|
|
|
|
q := query("tpl_layout", "base", "tpl_content", fmt.Sprintf("%s_%s", plural, action))
|
|
|
|
|
|
|
|
return fmt.Sprintf("/%s/%d/%s?%s", plural, id, action, q)
|
|
|
|
}
|