Commit b80ee4b2bc37bdc1ab97b7a2eb302badaa906cd2

Authored by Marko Tikvić
1 parent 832243a5df
Exists in master

new stuff

File was created 1 package webutility
2
3 import (
4 "fmt"
5 "time"
6 )
7
8 const (
9 YYYYMMDD_sl = "2006/01/02"
10 YYYYMMDD_ds = "2006-01-02"
11 YYYYMMDD_dt = "2006.01.02."
12
13 DDMMYYYY_sl = "02/01/2006"
14 DDMMYYYY_ds = "02-01-2006"
15 DDMMYYYY_dt = "02.01.2006."
16
17 YYYYMMDD_HHMMSS_sl = "2006/01/02 15:04:05"
18 YYYYMMDD_HHMMSS_ds = "2006-01-02 15:04:05"
19 YYYYMMDD_HHMMSS_dt = "2006.01.02. 15:04:05"
20
21 DDMMYYYY_HHMMSS_sl = "02/01/2006 15:04:05"
22 DDMMYYYY_HHMMSS_ds = "02-01-2006 15:04:05"
23 DDMMYYYY_HHMMSS_dt = "02.01.2006. 15:04:05"
24 )
25
26 func Systime() int64 {
27 return time.Now().Unix()
28 }
29
30 func DateToEpoch(date, format string) int64 {
31 t, err := time.Parse(format, date)
32 if err != nil {
33 fmt.Println(err.Error())
34 return 0
35 }
36 return t.Unix()
37 }
38
39 func EpochToDate(e int64, format string) string {
40 return time.Unix(e, 0).Format(format)
41 }
42
1 package webutility 1 package webutility
2 2
3 import ( 3 import (
4 "fmt"
5 "net/http" 4 "net/http"
6 "net/url" 5 "net/url"
7 "strings" 6 "strings"
8 ) 7 )
9 8
10 // Filter ... 9 // Filter ...
11 type Filter map[string][]string 10 type Filter map[string][]string
12 11
13 // Get ... 12 // Get ...
14 func (f Filter) Get(key string) (values []string, ok bool) { 13 func (f Filter) Get(key string) (values []string, ok bool) {
15 values, ok = f[key] 14 values, ok = f[key]
16 return values, ok 15 return values, ok
17 } 16 }
18 17
19 // Count ... 18 // Count ...
20 func (f Filter) Count() int { 19 func (f Filter) Count() int {
21 return len(f) 20 return len(f)
22 } 21 }
23 22
24 // Add ... 23 // Add ...
25 func (f Filter) Add(key, val string) { 24 func (f Filter) Add(key, val string) {
26 f[key] = append(f[key], val) 25 f[key] = append(f[key], val)
27 } 26 }
28 27
29 // ValueAt ... 28 // ValueAt ...
30 func (f Filter) ValueAt(val string, index int) string { 29 func (f Filter) ValueAt(val string, index int) string {
31 if filter, ok := f[val]; ok { 30 if filter, ok := f[val]; ok {
32 if len(filter) > index { 31 if len(filter) > index {
33 return filter[index] 32 return filter[index]
34 } 33 }
35 } 34 }
36 35
37 return "" 36 return ""
38 } 37 }
39 38
40 func (f Filter) validate(validFilters []string) (Filter, bool) { 39 func (f Filter) Validate(validFilters []string) (Filter, bool) {
41 goodFilters := make(Filter) 40 goodFilters := make(Filter)
42 cnt, len := 0, 0 41 cnt, len := 0, 0
43 for fi := range f { 42 for fi := range f {
44 len++ 43 len++
45 for _, v := range validFilters { 44 for _, v := range validFilters {
46 if fi == v { 45 if fi == v {
47 cnt++ 46 cnt++
48 goodFilters[fi] = f[fi] 47 goodFilters[fi] = f[fi]
49 } 48 }
50 } 49 }
51 } 50 }
52 51
53 result := true 52 result := true
54 if len > 0 && cnt == 0 { 53 if len > 0 && cnt == 0 {
55 // if no valid filters are found declare filtering request as invalid 54 // if no valid filters are found declare filtering request as invalid
56 result = false 55 result = false
57 } 56 }
58 57
59 return goodFilters, result 58 return goodFilters, result
60 } 59 }
61 60
62 // ParseFilters requires input in format: "param1::value1|param2::value2..." 61 // ParseFilters requires input in format: "param1::value1|param2::value2..."
63 func ParseFilters(req *http.Request, header string) (filters Filter) { 62 func ParseFilters(req *http.Request, header string) (filters Filter) {
64 q := req.FormValue(header) 63 q := req.FormValue(header)
65 q = strings.Trim(q, "\"") 64 q = strings.Trim(q, "\"")
66 kvp := strings.Split(q, "|") 65 kvp := strings.Split(q, "|")
67 filters = make(Filter, len(kvp)) 66 filters = make(Filter, len(kvp))
68 67
69 for i := range kvp { 68 for i := range kvp {
70 kv := strings.Split(kvp[i], "::") 69 kv := strings.Split(kvp[i], "::")
71 if len(kv) == 2 { 70 if len(kv) == 2 {
72 key, _ := url.QueryUnescape(kv[0]) 71 key, _ := url.QueryUnescape(kv[0])
73 72
74 // get values (if more than 1) 73 // get values (if more than 1)
75 vals := strings.Split(kv[1], ",") 74 vals := strings.Split(kv[1], ",")
76 for _, v := range vals { 75 for _, v := range vals {
77 u, _ := url.QueryUnescape(v) 76 u, _ := url.QueryUnescape(v)
78 filters[key] = append(filters[key], u) 77 filters[key] = append(filters[key], u)
79 } 78 }
80 } 79 }
81 } 80 }
82 81
83 return filters 82 return filters
84 } 83 }
85
86 // MakeFilterString is very dodgy, needs more robustness.
87 // TODO(marko)
88 func MakeFilterString(prefix string, filters Filter, validFilters []string) (res string, ok bool) {
89 if prefix != "" {
90 prefix += "."
91 }
92
93 if filters.Count() == 0 {
94 return "", true
95 }
96
97 filters, ok = filters.validate(validFilters)
98 if !ok {
99 return "", false
100 }
101
102 first := true
103 for k, filter := range filters {
104 symbol := "="
105
106 if first {
107 res += " where "
108 first = false
109 } else {
110 res += " and "
111 }
112
113 res += "("
114 for i, f := range filter {
115 if strings.HasPrefix(f, "<") || strings.HasPrefix(f, ">") || strings.HasPrefix(f, "!") {
116 symbol = string(f[0])
117 f = strings.TrimLeft(f, "<>!")
118 if strings.HasPrefix(f, "=") {
119 f = strings.TrimLeft(f, "=")
120 symbol += "="
121 }
122 }
123
124 res += fmt.Sprintf("%s%s %s '%s'", prefix, k, symbol, f)
125
126 if i < len(filter)-1 {
127 res += " or "
128 }
129 }
130 res += ")"
131 }
132
133 return res, ok
134 }
135 84
File was created 1 package webutility
2
3 import (
4 "fmt"
5 "math"
6 "math/big"
7 )
8
9 func RoundFloat64(f float64, dec int) float64 {
10 p := math.Pow(10, float64(dec))
11 return math.Round(f*p) / p
12 }
13
14 func NewBF(f float64, prec uint) *big.Float {
15 x := big.NewFloat(f)
16 x.SetPrec(prec)
17 return x
18 }
19
20 func AddBF(x, y *big.Float) *big.Float {
21 z := big.NewFloat(0.0)
22 z.SetPrec(x.Prec())
23 z.Add(x, y)
24 return z
25 }
26
27 func SubBF(x, y *big.Float) *big.Float {
28 z := big.NewFloat(0.0)
29 z.SetPrec(x.Prec())
30
31 yneg := big.NewFloat(0.0)
32 yneg.Neg(y)
33
34 z.Add(x, yneg)
35 return z
36 }
37
38 func MulBF(x, y *big.Float) *big.Float {
39 z := big.NewFloat(0.0)
40 z.SetPrec(x.Prec())
41 z.Mul(x, y)
42 return z
43 }
44
45 func DivBF(x, y *big.Float) *big.Float {
46 z := big.NewFloat(0.0)
47 z.SetPrec(x.Prec())
48 z.Quo(x, y)
49 return z
50 }
51
52 func BFtoFloat(f *big.Float) float64 {
53 v, _ := f.Float64()
54 return v
55 }
56
57 func Float64ToString(f float64) string {
58 return fmt.Sprintf("%.2f", f)
59 }
60
1 package webutility 1 package webutility
2 2
3 import ( 3 import (
4 "fmt" 4 "fmt"
5 "strconv"
6 ) 5 )
7 6
8 // ClampInt64 ... 7 // ClampInt64 ...
9 func ClampInt64(v, min, max int64) int64 { 8 func ClampInt64(v, min, max int64) int64 {
10 if v < min { 9 if v < min {
11 return min 10 return min
12 } else if v > max { 11 } else if v > max {
13 return max 12 return max
14 } 13 }
15 14
16 return v 15 return v
17 } 16 }
18 17
19 // InRangeInt64 ... 18 // InRangeInt64 ...
20 func InRangeInt64(v, min, max int64) bool { 19 func InRangeInt64(v, min, max int64) bool {
21 return (v >= min && v <= max) 20 return (v >= min && v <= max)
22 } 21 }
23 22
24 // StringToInt64 ...
25 func StringToInt64(s string) int64 {
26 i, _ := strconv.ParseInt(s, 10, 64)
27 return i
28 }
29
30 // StringToFloat64 ...
31 func StringToFloat64(s string) float64 {
32 f, _ := strconv.ParseFloat(s, 64)
33 return f
34 }
35
36 // Int64ToString ... 23 // Int64ToString ...
37 func Int64ToString(i int64) string { 24 func Int64ToString(i int64) string {
38 return fmt.Sprintf("%d", i) 25 return fmt.Sprintf("%d", i)
39 } 26 }
40 27
41 // BoolToInt64 ... 28 // BoolToInt64 ...
42 func BoolToInt64(b bool) int64 { 29 func BoolToInt64(b bool) int64 {
43 if b { 30 if b {
44 return 1 31 return 1
45 } 32 }
46 return 0 33 return 0
47 } 34 }
48 35
49 // Int64ToBool ... 36 // Int64ToBool ...
50 func Int64ToBool(i int64) bool { 37 func Int64ToBool(i int64) bool {
51 return i != 0 38 return i != 0
52 } 39 }
53 40
54 func StringToValidInt64(s string) (int64, bool) { 41 func MaxInt(vars ...int) (max int) {
55 i, err := strconv.ParseInt(s, 10, 64) 42 for _, v := range vars {
56 if err != nil { 43 if v > max {
57 return i, false 44 max = v
45 }
58 } 46 }
59 return i, true 47 return max
60 } 48 }
middleware/main.go
1 package middleware 1 package middleware
2 2
3 import ( 3 import (
4 "fmt" 4 "fmt"
5 "io/ioutil" 5 "io/ioutil"
6 "net/http" 6 "net/http"
7 "os"
8 "strings"
7 9
8 web "git.to-net.rs/marko.tikvic/webutility" 10 web "git.to-net.rs/marko.tikvic/webutility"
9 ) 11 )
10 12
11 func Headers(h http.HandlerFunc) http.HandlerFunc { 13 func Headers(h http.HandlerFunc) http.HandlerFunc {
12 return SetAccessControlHeaders(IgnoreOptionsRequests(ParseForm(h))) 14 return SetAccessControlHeaders(IgnoreOptionsRequests(ParseForm(h)))
13 } 15 }
14 16
15 func AuthUser(roles string, h http.HandlerFunc) http.HandlerFunc { 17 func AuthUser(roles string, h http.HandlerFunc) http.HandlerFunc {
16 return SetAccessControlHeaders(IgnoreOptionsRequests(ParseForm(Auth(roles, h)))) 18 return SetAccessControlHeaders(IgnoreOptionsRequests(ParseForm(Auth(roles, h))))
17 } 19 }
18 20
19 func AuthUserAndLog(roles string, h http.HandlerFunc) http.HandlerFunc { 21 func AuthUserAndLog(roles string, h http.HandlerFunc) http.HandlerFunc {
20 return SetAccessControlHeaders(IgnoreOptionsRequests(ParseForm(LogHTTP(Auth(roles, h))))) 22 return SetAccessControlHeaders(IgnoreOptionsRequests(ParseForm(LogHTTP(Auth(roles, h)))))
21 } 23 }
22 24
23 func LogTraffic(h http.HandlerFunc) http.HandlerFunc { 25 func LogTraffic(h http.HandlerFunc) http.HandlerFunc {
24 return SetAccessControlHeaders(IgnoreOptionsRequests(ParseForm(LogHTTP(h)))) 26 return SetAccessControlHeaders(IgnoreOptionsRequests(ParseForm(LogHTTP(h))))
25 } 27 }
26 28
27 func TrafficLogsHandler(w http.ResponseWriter, req *http.Request) { 29 func TrafficLogsHandler(w http.ResponseWriter, req *http.Request) {
28 switch req.Method { 30 switch req.Method {
29 case "GET": 31 case "GET":
30 files, err := ioutil.ReadDir(httpLogger.GetOutDir() + "/") 32 logfile := req.FormValue("logfile")
31 if err != nil { 33 if logfile == "" {
32 web.InternalServerError(w, req, err.Error()) 34 files, err := ioutil.ReadDir(httpLogger.GetOutDir() + "/")
33 return 35 if err != nil {
34 } 36 web.InternalServerError(w, req, err.Error())
37 return
38 }
35 39
36 web.SetContentType(w, "text/html; charset=utf-8") 40 errorLogs := make([]os.FileInfo, 0)
37 web.SetResponseStatus(w, http.StatusOK) 41 httpLogs := make([]os.FileInfo, 0)
38
39 web.WriteResponse(w, []byte("<body style='background-color: black; color: white'>"))
40 inputForm := `
41 <div>
42 <form action="/api/v1/logs" method="POST" target="_blank">
43 Username:<br>
44 <input type="text" name="username"><br>
45 Password:<br>
46 <input type="password" name="password"><br>
47 Log:<br>
48 <input type="text" name="logfile"><br>
49 <input type="submit" value="View">
50 </form>
51 </div>`
52 web.WriteResponse(w, []byte(inputForm))
53
54 web.WriteResponse(w, []byte("<table>"))
55 web.WriteResponse(w, []byte("<tr><th>Name</th><th>Size</th></tr>"))
56 for i := range files {
57 name := files[i].Name()
58 size := files[i].Size()
59 div := fmt.Sprintf(`<tr><td>%s</td><td style="text-align:right">%dB</td></tr>`, name, size)
60 web.WriteResponse(w, []byte(div))
61 }
62 web.WriteResponse(w, []byte("</table></body>"))
63 42
64 case "POST": 43 var errorLogsCount, httpLogsCount int
65 web.SetContentType(w, "text/html; charset=utf-8") 44 for _, f := range files {
45 if strings.HasPrefix(f.Name(), "err") {
46 errorLogs = append(errorLogs, f)
47 errorLogsCount++
48 } else if strings.HasPrefix(f.Name(), "http") {
49 httpLogs = append(httpLogs, f)
50 httpLogsCount++
51 }
52 }
66 53
67 logfile := req.FormValue("logfile") 54 web.SetContentType(w, "text/html; charset=utf-8")
68 content, err := web.ReadFileContent(httpLogger.GetOutDir() + "/" + logfile) 55 web.SetResponseStatus(w, http.StatusOK)
69 if err != nil { 56
70 web.InternalServerError(w, req, err.Error()) 57 web.WriteResponse(w, []byte(`
71 return 58 <body style='background-color: black; color: white'>
59 <table>
60 <tr>
61 <th>Error logs</th><th></th> <th style="width: 25px"></th>
62 <th>Traffic logs</th><th></th>
63 </tr>
64 `))
65
66 var (
67 div, name string
68 size int64
69 )
70
71 max := errorLogsCount
72 if httpLogsCount > errorLogsCount {
73 max = httpLogsCount
74 }
75
76 for i := 0; i < max; i++ {
77 div = "<tr>"
78
79 if i < errorLogsCount {
80 name = errorLogs[i].Name()
81 size = errorLogs[i].Size()
82 div += fmt.Sprintf(`
83 <td>
84 <a style="color: white"
85 href="/api/v1/logs?logfile=%s"
86 target="_blank">%s
87 </a>
88 </td>
89 <td style="color: white; text-align:right">%dB</td>`,
90 name, name, size,
91 )
92 } else {
93 div += fmt.Sprintf(`<td></td><td></td>`)
94 }
95
96 div += "<td></td>"
97
98 if i < httpLogsCount {
99 name := httpLogs[i].Name()
100 size := httpLogs[i].Size()
101 div += fmt.Sprintf(`
102 <td>
103 <a style="color: white"
104 href="/api/v1/logs?logfile=%s"
105 target="_blank">%s
106 </a></td>
107 <td style="color: white; text-align:right">%dB</td>`,
1 package webutility 1 package webutility
2 2
3 import ( 3 import (
4 "database/sql" 4 "database/sql"
5 "database/sql/driver" 5 "database/sql/driver"
6 "encoding/json" 6 "encoding/json"
7 "fmt" 7 "fmt"
8 "time" 8 "time"
9 ) 9 )
10 10
11 // NullBool is a wrapper for sql.NullBool with added JSON (un)marshalling 11 // NullBool is a wrapper for sql.NullBool with added JSON (un)marshalling
12 type NullBool sql.NullBool 12 type NullBool sql.NullBool
13 13
14 // Scan ... 14 // Scan ...
15 func (nb *NullBool) Scan(value interface{}) error { 15 func (nb *NullBool) Scan(value interface{}) error {
16 var b sql.NullBool 16 var b sql.NullBool
17 if err := b.Scan(value); err != nil { 17 if err := b.Scan(value); err != nil {
18 nb.Bool, nb.Valid = false, false 18 nb.Bool, nb.Valid = false, false
19 return err 19 return err
20 } 20 }
21 nb.Bool, nb.Valid = b.Bool, b.Valid 21 nb.Bool, nb.Valid = b.Bool, b.Valid
22 return nil 22 return nil
23 } 23 }
24 24
25 // Value ... 25 // Value ...
26 func (nb *NullBool) Value() (driver.Value, error) { 26 func (nb *NullBool) Value() (driver.Value, error) {
27 if !nb.Valid { 27 if !nb.Valid {
28 return nil, nil 28 return nil, nil
29 } 29 }
30 return nb.Bool, nil 30 return nb.Bool, nil
31 } 31 }
32 32
33 // MarshalJSON ... 33 // MarshalJSON ...
34 func (nb NullBool) MarshalJSON() ([]byte, error) { 34 func (nb NullBool) MarshalJSON() ([]byte, error) {
35 if nb.Valid { 35 if nb.Valid {
36 return json.Marshal(nb.Bool) 36 return json.Marshal(nb.Bool)
37 } 37 }
38 38
39 return json.Marshal(nil) 39 return json.Marshal(nil)
40 } 40 }
41 41
42 // UnmarshalJSON ... 42 // UnmarshalJSON ...
43 func (nb *NullBool) UnmarshalJSON(b []byte) error { 43 func (nb *NullBool) UnmarshalJSON(b []byte) error {
44 var temp *bool 44 var temp *bool
45 if err := json.Unmarshal(b, &temp); err != nil { 45 if err := json.Unmarshal(b, &temp); err != nil {
46 return err 46 return err
47 } 47 }
48 if temp != nil { 48 if temp != nil {
49 nb.Valid = true 49 nb.Valid = true
50 nb.Bool = *temp 50 nb.Bool = *temp
51 } else { 51 } else {
52 nb.Valid = false 52 nb.Valid = false
53 } 53 }
54 return nil 54 return nil
55 } 55 }
56 56
57 // SQLCast ... 57 // SQLCast ...
58 func (nb *NullBool) SQLCast() sql.NullBool { 58 func (nb *NullBool) SQLCast() sql.NullBool {
59 return sql.NullBool(*nb) 59 return sql.NullBool(*nb)
60 } 60 }
61 61
62 // NullString is a wrapper for sql.NullString with added JSON (un)marshalling 62 // NullString is a wrapper for sql.NullString with added JSON (un)marshalling
63 type NullString sql.NullString 63 type NullString sql.NullString
64 64
65 // Scan ... 65 // Scan ...
66 func (ns *NullString) Scan(value interface{}) error { 66 func (ns *NullString) Scan(value interface{}) error {
67 var s sql.NullString 67 var s sql.NullString
68 if err := s.Scan(value); err != nil { 68 if err := s.Scan(value); err != nil {
69 ns.String, ns.Valid = "", false 69 ns.String, ns.Valid = "", false
70 return err 70 return err
71 } 71 }
72 ns.String, ns.Valid = s.String, s.Valid 72 ns.String, ns.Valid = s.String, s.Valid
73 return nil 73 return nil
74 } 74 }
75 75
76 // Value ... 76 // Value ...
77 func (ns *NullString) Value() (driver.Value, error) { 77 func (ns *NullString) Value() (driver.Value, error) {
78 if !ns.Valid { 78 if !ns.Valid {
79 return nil, nil 79 return nil, nil
80 } 80 }
81 return ns.String, nil 81 return ns.String, nil
82 } 82 }
83 83
84 // MarshalJSON ... 84 // MarshalJSON ...
85 func (ns NullString) MarshalJSON() ([]byte, error) { 85 func (ns NullString) MarshalJSON() ([]byte, error) {
86 if ns.Valid { 86 if ns.Valid {
87 return json.Marshal(ns.String) 87 return json.Marshal(ns.String)
88 } 88 }
89 return json.Marshal(nil) 89 return json.Marshal(nil)
90 } 90 }
91 91
92 // UnmarshalJSON ... 92 // UnmarshalJSON ...
93 func (ns *NullString) UnmarshalJSON(b []byte) error { 93 func (ns *NullString) UnmarshalJSON(b []byte) error {
94 var temp *string 94 var temp *string
95 if err := json.Unmarshal(b, &temp); err != nil { 95 if err := json.Unmarshal(b, &temp); err != nil {
96 return err 96 return err
97 } 97 }
98 if temp != nil { 98 if temp != nil {
99 ns.Valid = true 99 ns.Valid = true
100 ns.String = *temp 100 ns.String = *temp
101 } else { 101 } else {
102 ns.Valid = false 102 ns.Valid = false
103 } 103 }
104 return nil 104 return nil
105 } 105 }
106 106
107 // SQLCast ... 107 // SQLCast ...
108 func (ns *NullString) SQLCast() sql.NullString { 108 func (ns *NullString) SQLCast() sql.NullString {
109 return sql.NullString(*ns) 109 return sql.NullString(*ns)
110 } 110 }
111 111
112 // NullInt64 is a wrapper for sql.NullInt64 with added JSON (un)marshalling 112 // NullInt64 is a wrapper for sql.NullInt64 with added JSON (un)marshalling
113 type NullInt64 sql.NullInt64 113 type NullInt64 sql.NullInt64
114 114
115 // Scan ... 115 // Scan ...
116 func (ni *NullInt64) Scan(value interface{}) error { 116 func (ni *NullInt64) Scan(value interface{}) error {
117 var i sql.NullInt64 117 var i sql.NullInt64
118 if err := i.Scan(value); err != nil { 118 if err := i.Scan(value); err != nil {
119 ni.Int64, ni.Valid = 0, false 119 ni.Int64, ni.Valid = 0, false
120 return err 120 return err
121 } 121 }
122 ni.Int64, ni.Valid = i.Int64, i.Valid 122 ni.Int64, ni.Valid = i.Int64, i.Valid
123 return nil 123 return nil
124 } 124 }
125 125
126 // ScanPtr ...
127 func (ni *NullInt64) ScanPtr(v interface{}) error {
128 if ip, ok := v.(*int64); ok && ip != nil {
129 return ni.Scan(*ip)
130 }
131 return nil
132 }
133
126 // Value ... 134 // Value ...
127 func (ni *NullInt64) Value() (driver.Value, error) { 135 func (ni *NullInt64) Value() (driver.Value, error) {
128 if !ni.Valid { 136 if !ni.Valid {
129 return nil, nil 137 return nil, nil
130 } 138 }
131 return ni.Int64, nil 139 return ni.Int64, nil
132 } 140 }
133 141
134 // MarshalJSON ... 142 // MarshalJSON ...
135 func (ni NullInt64) MarshalJSON() ([]byte, error) { 143 func (ni NullInt64) MarshalJSON() ([]byte, error) {
136 if ni.Valid { 144 if ni.Valid {
137 return json.Marshal(ni.Int64) 145 return json.Marshal(ni.Int64)
138 } 146 }
139 return json.Marshal(nil) 147 return json.Marshal(nil)
140 } 148 }
141 149
142 // UnmarshalJSON ... 150 // UnmarshalJSON ...
143 func (ni *NullInt64) UnmarshalJSON(b []byte) error { 151 func (ni *NullInt64) UnmarshalJSON(b []byte) error {
144 var temp *int64 152 var temp *int64
145 if err := json.Unmarshal(b, &temp); err != nil { 153 if err := json.Unmarshal(b, &temp); err != nil {
146 return err 154 return err
147 } 155 }
148 if temp != nil { 156 if temp != nil {
149 ni.Valid = true 157 ni.Valid = true
150 ni.Int64 = *temp 158 ni.Int64 = *temp
151 } else { 159 } else {
152 ni.Valid = false 160 ni.Valid = false
153 } 161 }
154 return nil 162 return nil
155 } 163 }
156 164
157 // SQLCast ... 165 // SQLCast ...
158 func (ni *NullInt64) SQLCast() sql.NullInt64 { 166 func (ni *NullInt64) SQLCast() sql.NullInt64 {
159 return sql.NullInt64(*ni) 167 return sql.NullInt64(*ni)
160 } 168 }
161 169
162 // NullFloat64 is a wrapper for sql.NullFloat64 with added JSON (un)marshalling 170 // NullFloat64 is a wrapper for sql.NullFloat64 with added JSON (un)marshalling
163 type NullFloat64 sql.NullFloat64 171 type NullFloat64 sql.NullFloat64
164 172
165 // Scan ... 173 // Scan ...
166 func (nf *NullFloat64) Scan(value interface{}) error { 174 func (nf *NullFloat64) Scan(value interface{}) error {
167 var f sql.NullFloat64 175 var f sql.NullFloat64
168 if err := f.Scan(value); err != nil { 176 if err := f.Scan(value); err != nil {
169 nf.Float64, nf.Valid = 0.0, false 177 nf.Float64, nf.Valid = 0.0, false
170 return err 178 return err
171 } 179 }
172 nf.Float64, nf.Valid = f.Float64, f.Valid 180 nf.Float64, nf.Valid = f.Float64, f.Valid
173 return nil 181 return nil
174 } 182 }
175 183
184 // ScanPtr ...
185 func (nf *NullFloat64) ScanPtr(v interface{}) error {
186 if fp, ok := v.(*float64); ok && fp != nil {
187 return nf.Scan(*fp)
188 }
189 return nil
190 }
191
176 // Value ... 192 // Value ...
177 func (nf *NullFloat64) Value() (driver.Value, error) { 193 func (nf *NullFloat64) Value() (driver.Value, error) {
178 if !nf.Valid { 194 if !nf.Valid {
179 return nil, nil 195 return nil, nil
180 } 196 }
181 return nf.Float64, nil 197 return nf.Float64, nil
182 } 198 }
183 199
184 // MarshalJSON ... 200 // MarshalJSON ...
185 func (nf NullFloat64) MarshalJSON() ([]byte, error) { 201 func (nf NullFloat64) MarshalJSON() ([]byte, error) {
186 if nf.Valid { 202 if nf.Valid {
187 return json.Marshal(nf.Float64) 203 return json.Marshal(nf.Float64)
188 } 204 }
189 return json.Marshal(nil) 205 return json.Marshal(nil)
190 } 206 }
191 207
192 // UnmarshalJSON ... 208 // UnmarshalJSON ...
193 func (nf *NullFloat64) UnmarshalJSON(b []byte) error { 209 func (nf *NullFloat64) UnmarshalJSON(b []byte) error {
194 var temp *float64 210 var temp *float64
195 if err := json.Unmarshal(b, &temp); err != nil { 211 if err := json.Unmarshal(b, &temp); err != nil {
196 return err 212 return err
197 } 213 }
198 if temp != nil { 214 if temp != nil {
199 nf.Valid = true 215 nf.Valid = true
200 nf.Float64 = *temp 216 nf.Float64 = *temp
201 } else { 217 } else {
202 nf.Valid = false 218 nf.Valid = false
203 } 219 }
204 return nil 220 return nil
205 } 221 }
206 222
207 // SQLCast ... 223 // SQLCast ...
208 func (nf *NullFloat64) SQLCast() sql.NullFloat64 { 224 func (nf *NullFloat64) SQLCast() sql.NullFloat64 {
209 return sql.NullFloat64(*nf) 225 return sql.NullFloat64(*nf)
210 } 226 }
211 227
212 // NullTime ... 228 // NullTime ...
213 type NullTime struct { 229 type NullTime struct {
214 Time time.Time 230 Time time.Time
215 Valid bool // Valid is true if Time is not NULL 231 Valid bool // Valid is true if Time is not NULL
216 } 232 }
217 233
218 // Scan ... 234 // Scan ...
219 func (nt *NullTime) Scan(value interface{}) (err error) { 235 func (nt *NullTime) Scan(value interface{}) (err error) {
220 if value == nil { 236 if value == nil {
221 nt.Time, nt.Valid = time.Time{}, false 237 nt.Time, nt.Valid = time.Time{}, false
222 return 238 return
223 } 239 }
224 240
225 switch v := value.(type) { 241 switch v := value.(type) {
226 case time.Time: 242 case time.Time:
227 nt.Time, nt.Valid = v, true 243 nt.Time, nt.Valid = v, true
228 return 244 return
229 case []byte: 245 case []byte:
230 nt.Time, err = parseDateTime(string(v), time.UTC) 246 nt.Time, err = parseDateTime(string(v), time.UTC)
231 nt.Valid = (err == nil) 247 nt.Valid = (err == nil)
232 return 248 return
233 case string: 249 case string:
234 nt.Time, err = parseDateTime(v, time.UTC) 250 nt.Time, err = parseDateTime(v, time.UTC)
235 nt.Valid = (err == nil) 251 nt.Valid = (err == nil)
236 return 252 return
237 } 253 }
238 254
239 nt.Valid = false 255 nt.Valid = false
240 return fmt.Errorf("Can't convert %T to time.Time", value) 256 return fmt.Errorf("Can't convert %T to time.Time", value)
241 } 257 }
242 258
243 // Value implements the driver Valuer interface. 259 // Value implements the driver Valuer interface.
244 func (nt NullTime) Value() (driver.Value, error) { 260 func (nt NullTime) Value() (driver.Value, error) {
245 if !nt.Valid { 261 if !nt.Valid {
246 return nil, nil 262 return nil, nil
247 } 263 }
248 return nt.Time, nil 264 return nt.Time, nil
249 } 265 }
250 266
251 // MarshalJSON ... 267 // MarshalJSON ...
252 func (nt NullTime) MarshalJSON() ([]byte, error) { 268 func (nt NullTime) MarshalJSON() ([]byte, error) {
253 if nt.Valid { 269 if nt.Valid {
254 format := nt.Time.Format("2006-01-02 15:04:05") 270 format := nt.Time.Format("2006-01-02 15:04:05")
255 return json.Marshal(format) 271 return json.Marshal(format)
256 } 272 }
257 return json.Marshal(nil) 273 return json.Marshal(nil)
258 } 274 }
259 275
260 // UnmarshalJSON ... 276 // UnmarshalJSON ...
261 func (nt *NullTime) UnmarshalJSON(b []byte) error { 277 func (nt *NullTime) UnmarshalJSON(b []byte) error {
262 var temp *time.Time 278 var temp *time.Time
263 var t1 time.Time 279 var t1 time.Time
264 var err error 280 var err error
265 281
266 s1 := string(b) 282 s1 := string(b)
267 s2 := s1[1 : len(s1)-1] 283 s2 := s1[1 : len(s1)-1]
268 if s1 == "null" { 284 if s1 == "null" {
269 temp = nil 285 temp = nil
270 } else { 286 } else {
271 t1, err = time.Parse("2006-01-02 15:04:05", s2) 287 t1, err = time.Parse("2006-01-02 15:04:05", s2)
272 if err != nil { 288 if err != nil {
273 return err 289 return err
274 } 290 }
275 temp = &t1 291 temp = &t1
276 } 292 }
277 293
278 if temp != nil { 294 if temp != nil {
279 nt.Valid = true 295 nt.Valid = true
280 nt.Time = *temp 296 nt.Time = *temp
281 } else { 297 } else {
282 nt.Valid = false 298 nt.Valid = false
283 } 299 }
284 return nil 300 return nil
285 } 301 }
286 302
287 func parseDateTime(str string, loc *time.Location) (t time.Time, err error) { 303 func parseDateTime(str string, loc *time.Location) (t time.Time, err error) {
288 base := "0000-00-00 00:00:00.0000000" 304 base := "0000-00-00 00:00:00.0000000"
289 timeFormat := "2006-01-02 15:04:05.999999" 305 timeFormat := "2006-01-02 15:04:05.999999"
290 switch len(str) { 306 switch len(str) {
291 case 10, 19, 21, 22, 23, 24, 25, 26: // up to "YYYY-MM-DD HH:MM:SS.MMMMMM" 307 case 10, 19, 21, 22, 23, 24, 25, 26: // up to "YYYY-MM-DD HH:MM:SS.MMMMMM"
292 if str == base[:len(str)] { 308 if str == base[:len(str)] {
293 return 309 return
294 } 310 }
295 t, err = time.Parse(timeFormat[:len(str)], str) 311 t, err = time.Parse(timeFormat[:len(str)], str)
296 default: 312 default:
297 err = fmt.Errorf("invalid time string: %s", str) 313 err = fmt.Errorf("invalid time string: %s", str)
298 return 314 return
299 } 315 }
300 316
301 // Adjust location 317 // Adjust location
302 if err == nil && loc != time.UTC { 318 if err == nil && loc != time.UTC {
303 y, mo, d := t.Date() 319 y, mo, d := t.Date()
304 h, mi, s := t.Clock() 320 h, mi, s := t.Clock()
305 t, err = time.Date(y, mo, d, h, mi, s, t.Nanosecond(), loc), nil 321 t, err = time.Date(y, mo, d, h, mi, s, t.Nanosecond(), loc), nil
306 } 322 }
307 323
308 return 324 return
309 } 325 }
310 326
1 package webutility 1 package webutility
2 2
3 import ( 3 import (
4 "database/sql" 4 "database/sql"
5 "encoding/json" 5 "encoding/json"
6 "errors" 6 "errors"
7 "fmt" 7 "fmt"
8 "net/http" 8 "net/http"
9 "strings" 9 "strings"
10 "sync" 10 "sync"
11 "time" 11 "time"
12 ) 12 )
13 13
14 var ( 14 var (
15 mu = &sync.Mutex{} 15 mu = &sync.Mutex{}
16 metadata = make(map[string]Payload) 16 metadata = make(map[string]Payload)
17 17
18 updateQue = make(map[string][]byte) 18 updateQue = make(map[string][]byte)
19 19
20 metadataDB *sql.DB 20 metadataDB *sql.DB
21 activeProject string 21 activeProject string
22 22
23 inited bool 23 inited bool
24 metaDriver string 24 metaDriver string
25 ) 25 )
26 26
27 // LangMap ... 27 // LangMap ...
28 type LangMap map[string]map[string]string 28 type LangMap map[string]map[string]string
29 29
30 // Field ... 30 // Field ...
31 type Field struct { 31 type Field struct {
32 Parameter string `json:"param"` 32 Parameter string `json:"param"`
33 Type string `json:"type"` 33 Type string `json:"type"`
34 Visible bool `json:"visible"` 34 Visible bool `json:"visible"`
35 Editable bool `json:"editable"` 35 Editable bool `json:"editable"`
36 } 36 }
37 37
38 // CorrelationField ... 38 // CorrelationField ...
39 type CorrelationField struct { 39 type CorrelationField struct {
40 Result string `json:"result"` 40 Result string `json:"result"`
41 Elements []string `json:"elements"` 41 Elements []string `json:"elements"`
42 Type string `json:"type"` 42 Type string `json:"type"`
43 } 43 }
44 44
45 // Translation ... 45 // Translation ...
46 type Translation struct { 46 type Translation struct {
47 Language string `json:"language"` 47 Language string `json:"language"`
48 FieldsLabels map[string]string `json:"fieldsLabels"` 48 FieldsLabels map[string]string `json:"fieldsLabels"`
49 } 49 }
50 50
51 // Payload ... 51 // Payload ...
52 type Payload struct { 52 type Payload struct {
53 Method string `json:"method"` 53 Method string `json:"method"`
54 Params map[string]string `json:"params"` 54 Params map[string]string `json:"params,omitempty"`
55 Lang []Translation `json:"lang"` 55 Lang []Translation `json:"lang,omitempty"`
56 Fields []Field `json:"fields"` 56 Fields []Field `json:"fields,omitempty"`
57 Correlations []CorrelationField `json:"correlationFields"` 57 Correlations []CorrelationField `json:"correlationFields,omitempty"`
58 IDField string `json:"idField"` 58 IDField string `json:"idField,omitempty"`
59 59
60 Links PaginationLinks `json:"_links"` 60 Links PaginationLinks `json:"_links,omitempty"`
61 61
62 // Data holds JSON payload. It can't be used for itteration. 62 // Data holds JSON payload. It can't be used for itteration.
63 Data interface{} `json:"data"` 63 Data interface{} `json:"data"`
64 } 64 }
65 65
66 // NewPayload returs a payload sceleton for entity described with key. 66 // NewPayload returs a payload sceleton for entity described with key.
67 func NewPayload(r *http.Request, key string) Payload { 67 func NewPayload(r *http.Request, key string) Payload {
68 p := metadata[key] 68 p := metadata[key]
69 p.Method = r.Method + " " + r.RequestURI 69 p.Method = r.Method + " " + r.RequestURI
70 return p 70 return p
71 } 71 }
72 72
73 func (p *Payload) addLang(code string, labels map[string]string) { 73 func (p *Payload) addLang(code string, labels map[string]string) {
74 t := Translation{ 74 t := Translation{
75 Language: code, 75 Language: code,
76 FieldsLabels: labels, 76 FieldsLabels: labels,
77 } 77 }
78 p.Lang = append(p.Lang, t) 78 p.Lang = append(p.Lang, t)
79 } 79 }
80 80
81 // SetData ... 81 // SetData ...
82 func (p *Payload) SetData(data interface{}) { 82 func (p *Payload) SetData(data interface{}) {
83 p.Data = data 83 p.Data = data
84 } 84 }
85 85
86 // SetPaginationInfo ... 86 // SetPaginationInfo ...
87 func (p *Payload) SetPaginationInfo(count, total int64, params PaginationParams) { 87 func (p *Payload) SetPaginationInfo(count, total int64, params PaginationParams) {
88 p.Links.Count = count 88 p.Links.Count = count
89 p.Links.Total = total 89 p.Links.Total = total
90 p.Links = params.links() 90 p.Links = params.links()
91 } 91 }
92 92
93 // InitPayloadsMetadata loads all payloads' information into 'metadata' variable. 93 // InitPayloadsMetadata loads all payloads' information into 'metadata' variable.
94 func InitPayloadsMetadata(drv string, db *sql.DB, project string) error { 94 func InitPayloadsMetadata(drv string, db *sql.DB, project string) error {
95 var err error 95 var err error
96 if drv != "ora" && drv != "mysql" { 96 if drv != "ora" && drv != "mysql" {
97 err = errors.New("driver not supported") 97 err = errors.New("driver not supported")
98 return err 98 return err
99 } 99 }
100 100
101 metaDriver = drv 101 metaDriver = drv
102 metadataDB = db 102 metadataDB = db
103 activeProject = project 103 activeProject = project
104 104
105 mu.Lock() 105 mu.Lock()
106 defer mu.Unlock() 106 defer mu.Unlock()
107 err = initMetadata(project) 107 err = initMetadata(project)
108 if err != nil { 108 if err != nil {
109 return err 109 return err
110 } 110 }
111 inited = true 111 inited = true
112 112
113 return nil 113 return nil
114 } 114 }
115 115
116 // EnableHotloading ... 116 // EnableHotloading ...
117 func EnableHotloading(interval int) { 117 func EnableHotloading(interval int) {
118 if interval > 0 { 118 if interval > 0 {
119 go hotload(interval) 119 go hotload(interval)
120 } 120 }
121 } 121 }
122 122
123 // GetMetadataForAllEntities ... 123 // GetMetadataForAllEntities ...
124 func GetMetadataForAllEntities() map[string]Payload { 124 func GetMetadataForAllEntities() map[string]Payload {
125 return metadata 125 return metadata
126 } 126 }
127 127
128 // GetMetadataForEntity ... 128 // GetMetadataForEntity ...
129 func GetMetadataForEntity(t string) (Payload, bool) { 129 func GetMetadataForEntity(t string) (Payload, bool) {
130 p, ok := metadata[t] 130 p, ok := metadata[t]
131 return p, ok 131 return p, ok
132 } 132 }
133 133
134 // QueEntityModelUpdate ... 134 // QueEntityModelUpdate ...
135 func QueEntityModelUpdate(entityType string, v interface{}) { 135 func QueEntityModelUpdate(entityType string, v interface{}) {
136 updateQue[entityType], _ = json.Marshal(v) 136 updateQue[entityType], _ = json.Marshal(v)
137 } 137 }
138 138
139 // UpdateEntityModels ... 139 // UpdateEntityModels ...
140 func UpdateEntityModels(command string) (total, upd, add int, err error) { 140 func UpdateEntityModels(command string) (total, upd, add int, err error) {
141 if command != "force" && command != "missing" { 141 if command != "force" && command != "missing" {
142 return total, 0, 0, errors.New("webutility: unknown command: " + command) 142 return total, 0, 0, errors.New("webutility: unknown command: " + command)
143 } 143 }
144 144
145 if !inited { 145 if !inited {
146 return 0, 0, 0, errors.New("webutility: metadata not initialized but update was tried") 146 return 0, 0, 0, errors.New("webutility: metadata not initialized but update was tried")
147 } 147 }
148 148
149 total = len(updateQue) 149 total = len(updateQue)
150 150
151 toUpdate := make([]string, 0) 151 toUpdate := make([]string, 0)
152 toAdd := make([]string, 0) 152 toAdd := make([]string, 0)
153 153
154 for k := range updateQue { 154 for k := range updateQue {
155 if _, exists := metadata[k]; exists { 155 if _, exists := metadata[k]; exists {
156 if command == "force" { 156 if command == "force" {
157 toUpdate = append(toUpdate, k) 157 toUpdate = append(toUpdate, k)
158 } 158 }
159 } else { 159 } else {
160 toAdd = append(toAdd, k) 160 toAdd = append(toAdd, k)
161 } 161 }
162 } 162 }
163 163
164 var uStmt *sql.Stmt 164 var uStmt *sql.Stmt
165 if metaDriver == "ora" { 165 if metaDriver == "ora" {
166 uStmt, err = metadataDB.Prepare("update entities set entity_model = :1 where entity_type = :2") 166 uStmt, err = metadataDB.Prepare("update entities set entity_model = :1 where entity_type = :2")
167 if err != nil { 167 if err != nil {
168 return 168 return
169 } 169 }
170 } else if metaDriver == "mysql" { 170 } else if metaDriver == "mysql" {
171 uStmt, err = metadataDB.Prepare("update entities set entity_model = ? where entity_type = ?") 171 uStmt, err = metadataDB.Prepare("update entities set entity_model = ? where entity_type = ?")
172 if err != nil { 172 if err != nil {
173 return 173 return
174 } 174 }
175 } 175 }
176 for _, k := range toUpdate { 176 for _, k := range toUpdate {
177 _, err = uStmt.Exec(string(updateQue[k]), k) 177 _, err = uStmt.Exec(string(updateQue[k]), k)
178 if err != nil { 178 if err != nil {
179 return 179 return
180 } 180 }
181 upd++ 181 upd++
182 } 182 }
183 183
184 blankPayload, _ := json.Marshal(Payload{}) 184 blankPayload, _ := json.Marshal(Payload{})
185 var iStmt *sql.Stmt 185 var iStmt *sql.Stmt
186 if metaDriver == "ora" { 186 if metaDriver == "ora" {
187 iStmt, err = metadataDB.Prepare("insert into entities(projekat, metadata, entity_type, entity_model) values(:1, :2, :3, :4)") 187 iStmt, err = metadataDB.Prepare("insert into entities(projekat, metadata, entity_type, entity_model) values(:1, :2, :3, :4)")
188 if err != nil { 188 if err != nil {
189 return 189 return
190 } 190 }
191 } else if metaDriver == "mysql" { 191 } else if metaDriver == "mysql" {
192 iStmt, err = metadataDB.Prepare("insert into entities(projekat, metadata, entity_type, entity_model) values(?, ?, ?, ?)") 192 iStmt, err = metadataDB.Prepare("insert into entities(projekat, metadata, entity_type, entity_model) values(?, ?, ?, ?)")
193 if err != nil { 193 if err != nil {
194 return 194 return
195 } 195 }
196 } 196 }
197 for _, k := range toAdd { 197 for _, k := range toAdd {
198 _, err = iStmt.Exec(activeProject, string(blankPayload), k, string(updateQue[k])) 198 _, err = iStmt.Exec(activeProject, string(blankPayload), k, string(updateQue[k]))
199 if err != nil { 199 if err != nil {
200 return 200 return
201 } 201 }
202 metadata[k] = Payload{} 202 metadata[k] = Payload{}
203 add++ 203 add++
204 } 204 }
205 205
206 return total, upd, add, nil 206 return total, upd, add, nil
207 } 207 }
208 208
209 func initMetadata(project string) error { 209 func initMetadata(project string) error {
210 rows, err := metadataDB.Query(`select 210 rows, err := metadataDB.Query(`select
211 entity_type, 211 entity_type,
212 metadata 212 metadata
213 from entities 213 from entities
214 where projekat = ` + fmt.Sprintf("'%s'", project)) 214 where projekat = ` + fmt.Sprintf("'%s'", project))
215 if err != nil { 215 if err != nil {
216 return err 216 return err
217 } 217 }
218 defer rows.Close() 218 defer rows.Close()
219 219
220 if len(metadata) > 0 { 220 if len(metadata) > 0 {
221 metadata = nil 221 metadata = nil
222 } 222 }
223 metadata = make(map[string]Payload) 223 metadata = make(map[string]Payload)
224 for rows.Next() { 224 for rows.Next() {
225 var name, load string 225 var name, load string
226 rows.Scan(&name, &load) 226 rows.Scan(&name, &load)
227 227
228 p := Payload{} 228 p := Payload{}
229 err := json.Unmarshal([]byte(load), &p) 229 err := json.Unmarshal([]byte(load), &p)
230 if err != nil { 230 if err != nil {
231 fmt.Printf("webutility: couldn't init: '%s' metadata: %s:\n%s\n", name, err.Error(), load) 231 fmt.Printf("webutility: couldn't init: '%s' metadata: %s:\n%s\n", name, err.Error(), load)
232 } else { 232 } else {
233 metadata[name] = p 233 metadata[name] = p
234 } 234 }
235 } 235 }
236 236
237 return nil 237 return nil
238 } 238 }
239 239
240 // LoadMetadataFromFile expects file in format: 240 // LoadMetadataFromFile expects file in format:
241 // 241 //
242 // [ payload A identifier ] 242 // [ payload A identifier ]
243 // key1 = value1 243 // key1 = value1
244 // key2 = value2 244 // key2 = value2
245 // ... 245 // ...
246 // [ payload B identifier ] 246 // [ payload B identifier ]
247 // key1 = value1 247 // key1 = value1
248 // key2 = value2 248 // key2 = value2
249 // ... 249 // ...
250 // 250 //
251 // TODO(marko): Currently supports only one hardcoded language... 251 // TODO(marko): Currently supports only one hardcoded language...
252 func LoadMetadataFromFile(path string) error { 252 func LoadMetadataFromFile(path string) error {
253 lines, err := ReadFileLines(path) 253 lines, err := ReadFileLines(path)
254 if err != nil { 254 if err != nil {
255 return err 255 return err
256 } 256 }
257 257
258 metadata = make(map[string]Payload) 258 metadata = make(map[string]Payload)
259 259
260 var name string 260 var name string
261 for i, l := range lines { 261 for i, l := range lines {
262 // skip empty lines 262 // skip empty lines
263 if l = strings.TrimSpace(l); len(l) == 0 { 263 if l = strings.TrimSpace(l); len(l) == 0 {
264 continue 264 continue
265 } 265 }
266 266
267 if IsWrappedWith(l, "[", "]") { 267 if IsWrappedWith(l, "[", "]") {
268 name = strings.Trim(l, "[]") 268 name = strings.Trim(l, "[]")
269 p := Payload{} 269 p := Payload{}
270 p.addLang("sr", make(map[string]string)) 270 p.addLang("sr", make(map[string]string))
271 metadata[name] = p 271 metadata[name] = p
272 continue 272 continue
273 } 273 }
274 274
275 if name == "" { 275 if name == "" {
276 return fmt.Errorf("webutility: LoadMetadataFromFile: error on line %d: [no header] [%s]", i+1, l) 276 return fmt.Errorf("webutility: LoadMetadataFromFile: error on line %d: [no header] [%s]", i+1, l)
277 } 277 }
278 278
279 parts := strings.Split(l, "=") 279 parts := strings.Split(l, "=")
280 if len(parts) != 2 { 280 if len(parts) != 2 {
281 return fmt.Errorf("webutility: LoadMetadataFromFile: error on line %d: [invalid format] [%s]", i+1, l) 281 return fmt.Errorf("webutility: LoadMetadataFromFile: error on line %d: [invalid format] [%s]", i+1, l)
282 } 282 }
283 283
284 k := strings.TrimSpace(parts[0]) 284 k := strings.TrimSpace(parts[0])
285 v := strings.TrimSpace(parts[1]) 285 v := strings.TrimSpace(parts[1])
286 if v != "-" { 286 if v != "-" {
287 metadata[name].Lang[0].FieldsLabels[k] = v 287 metadata[name].Lang[0].FieldsLabels[k] = v
288 } 288 }
289 } 289 }
290 290
291 return nil 291 return nil
292 } 292 }
293 293
294 func hotload(n int) { 294 func hotload(n int) {
295 entityScan := make(map[string]int64) 295 entityScan := make(map[string]int64)
296 firstCheck := true 296 firstCheck := true
297 for { 297 for {
298 time.Sleep(time.Duration(n) * time.Second) 298 time.Sleep(time.Duration(n) * time.Second)
299 rows, err := metadataDB.Query(`select 299 rows, err := metadataDB.Query(`select
300 ora_rowscn, 300 ora_rowscn,
301 entity_type 301 entity_type
302 from entities where projekat = ` + fmt.Sprintf("'%s'", activeProject)) 302 from entities where projekat = ` + fmt.Sprintf("'%s'", activeProject))
303 if err != nil { 303 if err != nil {
304 fmt.Printf("webutility: hotload failed: %v\n", err) 304 fmt.Printf("webutility: hotload failed: %v\n", err)
305 time.Sleep(time.Duration(n) * time.Second) 305 time.Sleep(time.Duration(n) * time.Second)
306 continue 306 continue
307 } 307 }
308 308
309 var toRefresh []string 309 var toRefresh []string
310 for rows.Next() { 310 for rows.Next() {
311 var scanID int64 311 var scanID int64
312 var entity string 312 var entity string
313 rows.Scan(&scanID, &entity) 313 rows.Scan(&scanID, &entity)
314 oldID, ok := entityScan[entity] 314 oldID, ok := entityScan[entity]
315 if !ok || oldID != scanID { 315 if !ok || oldID != scanID {
316 entityScan[entity] = scanID 316 entityScan[entity] = scanID
317 toRefresh = append(toRefresh, entity) 317 toRefresh = append(toRefresh, entity)
318 } 318 }
319 } 319 }
320 rows.Close() 320 rows.Close()
321 321
322 if rows.Err() != nil { 322 if rows.Err() != nil {
323 fmt.Printf("webutility: hotload rset error: %v\n", rows.Err()) 323 fmt.Printf("webutility: hotload rset error: %v\n", rows.Err())
324 time.Sleep(time.Duration(n) * time.Second) 324 time.Sleep(time.Duration(n) * time.Second)
325 continue 325 continue
326 } 326 }
327 327
328 if len(toRefresh) > 0 && !firstCheck { 328 if len(toRefresh) > 0 && !firstCheck {
329 mu.Lock() 329 mu.Lock()
330 refreshMetadata(toRefresh) 330 refreshMetadata(toRefresh)
331 mu.Unlock() 331 mu.Unlock()
332 } 332 }
333 if firstCheck { 333 if firstCheck {
334 firstCheck = false 334 firstCheck = false
335 } 335 }
336 } 336 }
337 } 337 }
338 338
339 func refreshMetadata(entities []string) { 339 func refreshMetadata(entities []string) {
340 for _, e := range entities { 340 for _, e := range entities {
341 fmt.Printf("refreshing %s\n", e) 341 fmt.Printf("refreshing %s\n", e)
342 rows, err := metadataDB.Query(`select 342 rows, err := metadataDB.Query(`select
343 metadata 343 metadata
344 from entities 344 from entities
345 where projekat = ` + fmt.Sprintf("'%s'", activeProject) + 345 where projekat = ` + fmt.Sprintf("'%s'", activeProject) +
346 ` and entity_type = ` + fmt.Sprintf("'%s'", e)) 346 ` and entity_type = ` + fmt.Sprintf("'%s'", e))
347 347
348 if err != nil { 348 if err != nil {
349 fmt.Printf("webutility: refresh: prep: %v\n", err) 349 fmt.Printf("webutility: refresh: prep: %v\n", err)
350 rows.Close() 350 rows.Close()
351 continue 351 continue
352 } 352 }
353 353
354 for rows.Next() { 354 for rows.Next() {
355 var load string 355 var load string
356 rows.Scan(&load) 356 rows.Scan(&load)
357 p := Payload{} 357 p := Payload{}
358 err := json.Unmarshal([]byte(load), &p) 358 err := json.Unmarshal([]byte(load), &p)
359 if err != nil { 359 if err != nil {
360 fmt.Printf("webutility: couldn't refresh: '%s' metadata: %s\n%s\n", e, err.Error(), load) 360 fmt.Printf("webutility: couldn't refresh: '%s' metadata: %s\n%s\n", e, err.Error(), load)
361 } else { 361 } else {
362 metadata[e] = p 362 metadata[e] = p
363 } 363 }
364 } 364 }
365 rows.Close() 365 rows.Close()
366 } 366 }
367 } 367 }
368 368
369 /* 369 /*
370 func ModifyMetadataForEntity(entityType string, p *Payload) error { 370 func ModifyMetadataForEntity(entityType string, p *Payload) error {
371 md, err := json.Marshal(*p) 371 md, err := json.Marshal(*p)
372 if err != nil { 372 if err != nil {
373 return err 373 return err
374 } 374 }
375 375
376 mu.Lock() 376 mu.Lock()
377 defer mu.Unlock() 377 defer mu.Unlock()
378 _, err = metadataDB.PrepAndExe(`update entities set 378 _, err = metadataDB.PrepAndExe(`update entities set
379 metadata = :1 379 metadata = :1
380 where projekat = :2 380 where projekat = :2
381 and entity_type = :3`, 381 and entity_type = :3`,
382 string(md), 382 string(md),
383 activeProject, 383 activeProject,
384 entityType) 384 entityType)
385 if err != nil { 385 if err != nil {
386 return err 386 return err
387 } 387 }
388 return nil 388 return nil
389 } 389 }
390 390
391 func DeleteEntityModel(entityType string) error { 391 func DeleteEntityModel(entityType string) error {
392 _, err := metadataDB.PrepAndExe("delete from entities where entity_type = :1", entityType) 392 _, err := metadataDB.PrepAndExe("delete from entities where entity_type = :1", entityType)
393 if err == nil { 393 if err == nil {
394 mu.Lock() 394 mu.Lock()
395 delete(metadata, entityType) 395 delete(metadata, entityType)
396 mu.Unlock() 396 mu.Unlock()
397 } 397 }
398 return err 398 return err
399 } 399 }
400 */ 400 */
401 401
1 package webutility 1 package webutility
2 2
3 type QSortDirection int 3 type QSortDirection int
4 4
5 const ( 5 const (
6 QSortAscending = 0 6 QSortAscending QSortDirection = iota
7 QSortDescending = 1 7 QSortDescending
8 ) 8 )
9 9
10 // QuickSortable is an interface for quicksorting slices. 10 // QuickSortable is an interface for quicksorting slices.
11 type QuickSortable interface { 11 type QuickSortable interface {
12 Swap(i, j int) 12 Swap(i, j int)
13 Compare(i, j int) int 13 Compare(i, j int) int
14 Len() int 14 Len() int
15 } 15 }
16 16
17 // Quicksort quicksorts que. 17 // Quicksort quicksorts que.
18 func Quicksort(que QuickSortable, low, high int, dir QSortDirection) { 18 func Quicksort(que QuickSortable, low, high int, dir QSortDirection) {
19 if low >= high { 19 if low >= high {
20 return 20 return
21 } 21 }
22 22
23 if dir != QSortAscending && dir != QSortDescending { 23 if dir != QSortAscending && dir != QSortDescending {
24 return 24 return
25 } 25 }
26 26
27 index := partition(que, low, high, dir) 27 index := partition(que, low, high, dir)
28 Quicksort(que, low, index-1, dir) 28 Quicksort(que, low, index-1, dir)
29 Quicksort(que, index+1, high, dir) 29 Quicksort(que, index+1, high, dir)
30 } 30 }
31 31
32 func partition(que QuickSortable, low, high int, dir QSortDirection) int { 32 func partition(que QuickSortable, low, high int, dir QSortDirection) int {
33 swap := 0
34 // -1 -> i > j
35 // 1 -> i < j
36 if dir == QSortDescending {
37 swap = -1
38 } else {
39 swap = 1
40 }
41
33 i := low - 1 42 i := low - 1
34 for j := low; j <= high-1; j++ { 43 for j := low; j <= high-1; j++ {
35 if dir == QSortAscending { 44 if que.Compare(j, high) == swap {
36 if que.Compare(j, high) == -1 { 45 i++
37 i++ 46 que.Swap(i, j)
38 que.Swap(i, j)
39 }
40 } else if dir == QSortDescending {
41 if que.Compare(j, high) == 1 {
42 i++
43 que.Swap(i, j)
44 }
1 package webutility 1 package webutility
2 2
3 import ( 3 import (
4 "fmt" 4 "fmt"
5 "strconv" 5 "strconv"
6 "strings" 6 "strings"
7 "unicode" 7 "unicode"
8 ) 8 )
9 9
10 const sanitisationPatern = "\"';&*<>=\\`:" 10 const sanitisationPatern = "\"';&*<>=\\`:"
11 11
12 // SanitiseString removes characters from s found in patern and returns new modified string. 12 // SanitiseString removes characters from s found in patern and returns new modified string.
13 func SanitiseString(s string) string { 13 func SanitiseString(s string) string {
14 return ReplaceAny(s, sanitisationPatern, "") 14 return ReplaceAny(s, sanitisationPatern, "")
15 } 15 }
16 16
17 // IsWrappedWith ... 17 // IsWrappedWith ...
18 func IsWrappedWith(src, begin, end string) bool { 18 func IsWrappedWith(src, begin, end string) bool {
19 return strings.HasPrefix(src, begin) && strings.HasSuffix(src, end) 19 return strings.HasPrefix(src, begin) && strings.HasSuffix(src, end)
20 } 20 }
21 21
22 // ParseInt64Arr ... 22 // ParseInt64Arr ...
23 func ParseInt64Arr(s, sep string) (arr []int64) { 23 func ParseInt64Arr(s, sep string) (arr []int64) {
24 s = strings.TrimSpace(s) 24 s = strings.TrimSpace(s)
25 if s != "" { 25 if s == "" {
26 parts := strings.Split(s, sep) 26 return
27 arr = make([]int64, len(parts)) 27 }
28 for i, p := range parts { 28 parts := strings.Split(s, sep)
29 num := StringToInt64(p) 29 arr = make([]int64, len(parts))
30 arr[i] = num 30 for i, p := range parts {
31 } 31 num := StringToInt64(p)
32 arr[i] = num
32 } 33 }
33 34
34 return arr 35 return arr
35 } 36 }
36 37
37 // Int64SliceToString ... 38 // Int64SliceToString ...
38 func Int64SliceToString(arr []int64) (s string) { 39 func Int64SliceToString(arr []int64) (s string) {
39 if len(arr) == 0 { 40 if len(arr) == 0 {
40 return "" 41 return ""
41 } 42 }
42 43
43 s += fmt.Sprintf("%d", arr[0]) 44 s += fmt.Sprintf("%d", arr[0])
44 for i := 1; i < len(arr); i++ { 45 for i := 1; i < len(arr); i++ {
45 s += fmt.Sprintf(",%d", arr[i]) 46 s += fmt.Sprintf(",%d", arr[i])
46 } 47 }
47 48
48 return s 49 return s
49 } 50 }
50 51
51 // CombineStrings ... 52 // CombineStrings ...
52 func CombineStrings(s1, s2, s3 string) string { 53 func CombineStrings(s1, s2, s3 string) string {
53 s1 = strings.TrimSpace(s1) 54 s1 = strings.TrimSpace(s1)
54 s2 = strings.TrimSpace(s2) 55 s2 = strings.TrimSpace(s2)
55 56
56 if s1 != "" && s2 != "" { 57 if s1 != "" && s2 != "" {
57 s1 += s3 + s2 58 s1 += s3 + s2
58 } else { 59 } else {
59 s1 += s2 60 s1 += s2
60 } 61 }
61 62
62 return s1 63 return s1
63 } 64 }
64 65
65 // ReplaceAny replaces any of the characters from patern found in s with r and returns a new resulting string. 66 // ReplaceAny replaces any of the characters from patern found in s with r and returns a new resulting string.
66 func ReplaceAny(s, patern, r string) (n string) { 67 func ReplaceAny(s, patern, r string) (n string) {
67 n = s 68 n = s
68 for _, c := range patern { 69 for _, c := range patern {
69 n = strings.Replace(n, string(c), r, -1) 70 n = strings.Replace(n, string(c), r, -1)
70 } 71 }
71 return n 72 return n
72 } 73 }
73 74
74 // StringToBool ... 75 // StringToBool ...
75 func StringToBool(s string) bool { 76 func StringToBool(s string) bool {
76 res, _ := strconv.ParseBool(s) 77 res, _ := strconv.ParseBool(s)
77 return res 78 return res
78 } 79 }
79 80
80 // BoolToString ... 81 // BoolToString ...
81 func BoolToString(b bool) string { 82 func BoolToString(b bool) string {
82 return fmt.Sprintf("%b", b) 83 return fmt.Sprintf("%b", b)
83 } 84 }
84 85
85 // StringSliceContains ... 86 // StringSliceContains ...
86 func StringSliceContains(slice []string, s string) bool { 87 func StringSliceContains(slice []string, s string) bool {
87 for i := range slice { 88 for i := range slice {
88 if slice[i] == s { 89 if slice[i] == s {
89 return true 90 return true
90 } 91 }
91 } 92 }
92 return false 93 return false
93 } 94 }
94 95
95 func SplitString(s, sep string) (res []string) { 96 func SplitString(s, sep string) (res []string) {
96 parts := strings.Split(s, sep) 97 parts := strings.Split(s, sep)
97 for _, p := range parts { 98 for _, p := range parts {
98 if p != "" { 99 if p != "" {
99 res = append(res, p) 100 res = append(res, p)
100 } 101 }
101 } 102 }
102 return res 103 return res
103 } 104 }
104 105
105 // StringAt ... 106 // StringAt ...
106 func StringAt(s string, index int) string { 107 func StringAt(s string, index int) string {
107 if len(s)-1 < index || index < 0 { 108 if len(s)-1 < index || index < 0 {
108 return "" 109 return ""
109 } 110 }
110 111
111 return string(s[index]) 112 return string(s[index])
112 } 113 }
113 114
114 // SplitText ... 115 // SplitText ...
115 func SplitText(s string, maxLen int) (lines []string) { 116 func SplitText(s string, maxLen int) (lines []string) {
116 runes := []rune(s) 117 runes := []rune(s)
117 118
118 i, start, sep, l := 0, 0, 0, 0 119 i, start, sep, l := 0, 0, 0, 0
119 for i = 0; i < len(runes); i++ { 120 for i = 0; i < len(runes); i++ {
120 c := runes[i] 121 c := runes[i]
121 122
122 if unicode.IsSpace(c) { 123 if unicode.IsSpace(c) {
123 sep = i 124 sep = i
124 } 125 }
125 126
126 if c == '\n' { 127 if c == '\n' {
127 if start != sep { 128 if start != sep {
128 lines = append(lines, string(runes[start:sep])) 129 lines = append(lines, string(runes[start:sep]))
129 } 130 }
130 start = i 131 start = i
131 sep = i 132 sep = i
132 l = 0 133 l = 0
133 } else if l >= maxLen { 134 } else if l >= maxLen {
134 if start != sep { 135 if start != sep {
135 lines = append(lines, string(runes[start:sep])) 136 lines = append(lines, string(runes[start:sep]))
136 sep = i 137 sep = i
137 start = i - 1 138 start = i - 1
138 l = 0 139 l = 0
139 } 140 }
140 } else { 141 } else {
141 l++ 142 l++
142 } 143 }
143 } 144 }
144 if start != i-1 { 145 if start != i-1 {
145 lines = append(lines, string(runes[start:i-1])) 146 lines = append(lines, string(runes[start:i-1]))
146 } 147 }
147 148
148 return lines 149 return lines
149 } 150 }
150 151
151 func CutTextWithThreeDots(txt string, maxLen int) string { 152 func CutTextWithThreeDots(txt string, maxLen int) string {
152 if len(txt) < maxLen || len(txt) <= 3 { 153 if len(txt) < maxLen || len(txt) <= 3 {
153 return txt 154 return txt
154 } 155 }
155 156
156 return txt[:maxLen-3] + "..." 157 return txt[:maxLen-3] + "..."
157 } 158 }
158 159
159 const threeDots = "\u2056\u2056\u2056" 160 const threeDots = "\u2056\u2056\u2056"
160 161
161 func LimitTextWithThreeDots(txt string, maxLen int) string { 162 func LimitTextWithThreeDots(txt string, maxLen int) string {
162 if len(txt) <= maxLen { 163 if len(txt) <= maxLen {
163 return txt 164 return txt
164 } 165 }
165 166
166 return txt[:maxLen] + threeDots 167 return txt[:maxLen] + threeDots
167 } 168 }
168 169
169 func LimitMSWordTextWithThreeDots(txt string, maxLen int) string { 170 func LimitMSWordTextWithThreeDots(txt string, maxLen int) string {
170 if len(txt) <= maxLen { 171 if len(txt) <= maxLen {
171 return txt 172 return txt
172 } 173 }
173 174
174 return txt[:maxLen] + "..." 175 return txt[:maxLen] + "..."
175 } 176 }
176 177
178 func ThreeDots(txt string, maxLen int) string {
179 if len(txt) <= maxLen {
180 return txt
181 }
182
183 return txt[:maxLen] + "..."
184 }
185
177 // SplitStringAtWholeWords ... 186 // SplitStringAtWholeWords ...
178 func SplitStringAtWholeWords(s string, maxLen int) (res []string) { 187 func SplitStringAtWholeWords(s string, maxLen int) (res []string) {
179 parts := strings.Split(s, " ") 188 parts := strings.Split(s, " ")
180 189
181 res = append(res, parts[0]) 190 res = append(res, parts[0])
182 i := 0 191 i := 0
183 for j := 1; j < len(parts); j++ { 192 for j := 1; j < len(parts); j++ {
184 p := strings.TrimSpace(parts[j]) 193 p := strings.TrimSpace(parts[j])
185 if len(p) > maxLen { 194 if len(p) > maxLen {
186 // TODO(marko): check if maxLen is >= 3 195 // TODO(marko): check if maxLen is >= 3
187 p = p[0 : maxLen-3] 196 p = p[0 : maxLen-3]
188 p += "..." 197 p += "..."
189 } 198 }
190 if len(res[i])+len(p)+1 <= maxLen { 199 if len(res[i])+len(p)+1 <= maxLen {
191 res[i] += " " + p 200 res[i] += " " + p
192 } else { 201 } else {
193 res = append(res, p) 202 res = append(res, p)
194 i++ 203 i++
195 } 204 }
196 } 205 }
197 206
198 return res 207 return res
199 } 208 }
209
210 // StringToInt64 ...
211 func StringToInt64(s string) int64 {
212 i, _ := strconv.ParseInt(s, 10, 64)
213 return i
214 }
215
216 // StringToFloat64 ...
217 func StringToFloat64(s string) float64 {
218 f, _ := strconv.ParseFloat(s, 64)
219 return f
220 }
221
222 func StringToValidInt64(s string) (int64, bool) {
223 i, err := strconv.ParseInt(s, 10, 64)
224 if err != nil {
225 return i, false
226 }
227 return i, true
228 }
200 229