Commit 707782344fd94987d3cd7c8426d674dd6d7b16a1

Authored by Marko Tikvić
1 parent 954ce8ddd9
Exists in master

lint; vet

1 package webutility 1 package webutility
2 2
3 import ( 3 import (
4 "crypto/rand" 4 "crypto/rand"
5 "crypto/sha256" 5 "crypto/sha256"
6 "encoding/hex" 6 "encoding/hex"
7 "errors" 7 "errors"
8 "net/http" 8 "net/http"
9 "strings" 9 "strings"
10 "time" 10 "time"
11 11
12 "github.com/dgrijalva/jwt-go" 12 "github.com/dgrijalva/jwt-go"
13 ) 13 )
14 14
15 var _issuer = "webutility" 15 var _issuer = "webutility"
16 var _secret = "webutility" 16 var _secret = "webutility"
17 17
18 // TokenClaims are JWT token claims. 18 // TokenClaims are JWT token claims.
19 type TokenClaims struct { 19 type TokenClaims struct {
20 // extending a struct 20 // extending a struct
21 jwt.StandardClaims 21 jwt.StandardClaims
22 22
23 // custom claims 23 // custom claims
24 Token string `json:"access_token"` 24 Token string `json:"access_token"`
25 TokenType string `json:"token_type"` 25 TokenType string `json:"token_type"`
26 Username string `json:"username"` 26 Username string `json:"username"`
27 RoleName string `json:"role"` 27 RoleName string `json:"role"`
28 RoleID int64 `json:"role_id"` 28 RoleID int64 `json:"role_id"`
29 ExpiresIn int64 `json:"expires_in"` 29 ExpiresIn int64 `json:"expires_in"`
30 } 30 }
31 31
32 // InitJWT ...
32 func InitJWT(issuer, secret string) { 33 func InitJWT(issuer, secret string) {
33 _issuer = issuer 34 _issuer = issuer
34 _secret = secret 35 _secret = secret
35 } 36 }
36 37
37 // ValidateHash hashes pass and salt and returns comparison result with resultHash 38 // ValidateHash hashes pass and salt and returns comparison result with resultHash
38 func ValidateHash(pass, salt, resultHash string) (bool, error) { 39 func ValidateHash(pass, salt, resultHash string) (bool, error) {
39 hash, _, err := CreateHash(pass, salt) 40 hash, _, err := CreateHash(pass, salt)
40 if err != nil { 41 if err != nil {
41 return false, err 42 return false, err
42 } 43 }
43 res := hash == resultHash 44 res := hash == resultHash
44 return res, nil 45 return res, nil
45 } 46 }
46 47
47 // CreateHash hashes str using SHA256. 48 // CreateHash hashes str using SHA256.
48 // If the presalt parameter is not provided CreateHash will generate new salt string. 49 // If the presalt parameter is not provided CreateHash will generate new salt string.
49 // Returns hash and salt strings or an error if it fails. 50 // Returns hash and salt strings or an error if it fails.
50 func CreateHash(str, presalt string) (hash, salt string, err error) { 51 func CreateHash(str, presalt string) (hash, salt string, err error) {
51 // chech if message is presalted 52 // chech if message is presalted
52 if presalt == "" { 53 if presalt == "" {
53 salt, err = randomSalt() 54 salt, err = randomSalt()
54 if err != nil { 55 if err != nil {
55 return "", "", err 56 return "", "", err
56 } 57 }
57 } else { 58 } else {
58 salt = presalt 59 salt = presalt
59 } 60 }
60 61
61 // convert strings to raw byte slices 62 // convert strings to raw byte slices
62 rawstr := []byte(str) 63 rawstr := []byte(str)
63 rawsalt, err := hex.DecodeString(salt) 64 rawsalt, err := hex.DecodeString(salt)
64 if err != nil { 65 if err != nil {
65 return "", "", err 66 return "", "", err
66 } 67 }
67 68
68 rawdata := make([]byte, len(rawstr)+len(rawsalt)) 69 rawdata := make([]byte, len(rawstr)+len(rawsalt))
69 rawdata = append(rawdata, rawstr...) 70 rawdata = append(rawdata, rawstr...)
70 rawdata = append(rawdata, rawsalt...) 71 rawdata = append(rawdata, rawsalt...)
71 72
72 // hash message + salt 73 // hash message + salt
73 hasher := sha256.New() 74 hasher := sha256.New()
74 hasher.Write(rawdata) 75 hasher.Write(rawdata)
75 rawhash := hasher.Sum(nil) 76 rawhash := hasher.Sum(nil)
76 77
77 hash = hex.EncodeToString(rawhash) 78 hash = hex.EncodeToString(rawhash)
78 return hash, salt, nil 79 return hash, salt, nil
79 } 80 }
80 81
81 // CreateAuthToken returns JWT token with encoded username, role, expiration date and issuer claims. 82 // CreateAuthToken returns JWT token with encoded username, role, expiration date and issuer claims.
82 // It returns an error if it fails. 83 // It returns an error if it fails.
83 func CreateAuthToken(username string, roleName string, roleID int64) (TokenClaims, error) { 84 func CreateAuthToken(username string, roleName string, roleID int64) (TokenClaims, error) {
84 t0 := (time.Now()).Unix() 85 t0 := (time.Now()).Unix()
85 t1 := (time.Now().Add(time.Hour * 24 * 7)).Unix() 86 t1 := (time.Now().Add(time.Hour * 24 * 7)).Unix()
86 claims := TokenClaims{ 87 claims := TokenClaims{
87 TokenType: "Bearer", 88 TokenType: "Bearer",
88 Username: username, 89 Username: username,
89 RoleName: roleName, 90 RoleName: roleName,
90 RoleID: roleID, 91 RoleID: roleID,
91 ExpiresIn: t1 - t0, 92 ExpiresIn: t1 - t0,
92 } 93 }
93 // initialize jwt.StandardClaims fields (anonymous struct) 94 // initialize jwt.StandardClaims fields (anonymous struct)
94 claims.IssuedAt = t0 95 claims.IssuedAt = t0
95 claims.ExpiresAt = t1 96 claims.ExpiresAt = t1
96 claims.Issuer = _issuer 97 claims.Issuer = _issuer
97 98
98 jwtToken := jwt.NewWithClaims(jwt.SigningMethodHS256, claims) 99 jwtToken := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
99 token, err := jwtToken.SignedString([]byte(_secret)) 100 token, err := jwtToken.SignedString([]byte(_secret))
100 if err != nil { 101 if err != nil {
101 return TokenClaims{}, err 102 return TokenClaims{}, err
102 } 103 }
103 claims.Token = token 104 claims.Token = token
104 return claims, nil 105 return claims, nil
105 } 106 }
106 107
107 // RefreshAuthToken returns new JWT token with same claims contained in tok but with prolonged expiration date. 108 // RefreshAuthToken returns new JWT token with same claims contained in tok but with prolonged expiration date.
108 // It returns an error if it fails. 109 // It returns an error if it fails.
109 func RefreshAuthToken(tok string) (TokenClaims, error) { 110 func RefreshAuthToken(tok string) (TokenClaims, error) {
110 token, err := jwt.ParseWithClaims(tok, &TokenClaims{}, secretFunc) 111 token, err := jwt.ParseWithClaims(tok, &TokenClaims{}, secretFunc)
111 if err != nil { 112 if err != nil {
112 if validation, ok := err.(*jwt.ValidationError); ok { 113 if validation, ok := err.(*jwt.ValidationError); ok {
113 // don't return error if token is expired 114 // don't return error if token is expired
114 // just extend it 115 // just extend it
115 if !(validation.Errors&jwt.ValidationErrorExpired != 0) { 116 if !(validation.Errors&jwt.ValidationErrorExpired != 0) {
116 return TokenClaims{}, err 117 return TokenClaims{}, err
117 } 118 }
118 } else { 119 } else {
119 return TokenClaims{}, err 120 return TokenClaims{}, err
120 } 121 }
121 } 122 }
122 123
123 // type assertion 124 // type assertion
124 claims, ok := token.Claims.(*TokenClaims) 125 claims, ok := token.Claims.(*TokenClaims)
125 if !ok { 126 if !ok {
126 return TokenClaims{}, errors.New("token is not valid") 127 return TokenClaims{}, errors.New("token is not valid")
127 } 128 }
128 129
129 // extend token expiration date 130 // extend token expiration date
130 return CreateAuthToken(claims.Username, claims.RoleName, claims.RoleID) 131 return CreateAuthToken(claims.Username, claims.RoleName, claims.RoleID)
131 } 132 }
132 133
134 // AuthCheck ...
133 func AuthCheck(req *http.Request, roles string) (*TokenClaims, error) { 135 func AuthCheck(req *http.Request, roles string) (*TokenClaims, error) {
134 // validate token and check expiration date 136 // validate token and check expiration date
135 claims, err := GetTokenClaims(req) 137 claims, err := GetTokenClaims(req)
136 if err != nil { 138 if err != nil {
137 return claims, err 139 return claims, err
138 } 140 }
139 141
140 if roles == "" { 142 if roles == "" {
141 return claims, nil 143 return claims, nil
142 } 144 }
143 145
144 // check if token has expired 146 // check if token has expired
145 if claims.ExpiresAt < (time.Now()).Unix() { 147 if claims.ExpiresAt < (time.Now()).Unix() {
146 return claims, errors.New("token has expired") 148 return claims, errors.New("token has expired")
147 } 149 }
148 150
149 if roles == "*" { 151 if roles == "*" {
150 return claims, nil 152 return claims, nil
151 } 153 }
152 154
153 parts := strings.Split(roles, ",") 155 parts := strings.Split(roles, ",")
154 for i, _ := range parts { 156 for i := range parts {
155 r := strings.Trim(parts[i], " ") 157 r := strings.Trim(parts[i], " ")
156 if claims.RoleName == r { 158 if claims.RoleName == r {
157 return claims, nil 159 return claims, nil
158 } 160 }
159 } 161 }
160 162
161 return claims, nil 163 return claims, nil
162 } 164 }
163 165
164 // GetTokenClaims extracts JWT claims from Authorization header of req. 166 // GetTokenClaims extracts JWT claims from Authorization header of req.
165 // Returns token claims or an error. 167 // Returns token claims or an error.
166 func GetTokenClaims(req *http.Request) (*TokenClaims, error) { 168 func GetTokenClaims(req *http.Request) (*TokenClaims, error) {
167 // check for and strip 'Bearer' prefix 169 // check for and strip 'Bearer' prefix
168 var tokstr string 170 var tokstr string
169 authHead := req.Header.Get("Authorization") 171 authHead := req.Header.Get("Authorization")
170 if ok := strings.HasPrefix(authHead, "Bearer "); ok { 172 if ok := strings.HasPrefix(authHead, "Bearer "); ok {
171 tokstr = strings.TrimPrefix(authHead, "Bearer ") 173 tokstr = strings.TrimPrefix(authHead, "Bearer ")
172 } else { 174 } else {
173 return &TokenClaims{}, errors.New("authorization header in incomplete") 175 return &TokenClaims{}, errors.New("authorization header in incomplete")
174 } 176 }
175 177
176 token, err := jwt.ParseWithClaims(tokstr, &TokenClaims{}, secretFunc) 178 token, err := jwt.ParseWithClaims(tokstr, &TokenClaims{}, secretFunc)
177 if err != nil { 179 if err != nil {
178 return &TokenClaims{}, err 180 return &TokenClaims{}, err
179 } 181 }
180 182
181 // type assertion 183 // type assertion
182 claims, ok := token.Claims.(*TokenClaims) 184 claims, ok := token.Claims.(*TokenClaims)
183 if !ok || !token.Valid { 185 if !ok || !token.Valid {
184 return &TokenClaims{}, errors.New("token is not valid") 186 return &TokenClaims{}, errors.New("token is not valid")
185 } 187 }
186 188
187 return claims, nil 189 return claims, nil
188 } 190 }
189 191
190 // randomSalt returns a string of 32 random characters. 192 // randomSalt returns a string of 32 random characters.
191 func randomSalt() (s string, err error) { 193 func randomSalt() (s string, err error) {
192 const saltSize = 32 194 const saltSize = 32
193 195
194 rawsalt := make([]byte, saltSize) 196 rawsalt := make([]byte, saltSize)
195 197
196 _, err = rand.Read(rawsalt) 198 _, err = rand.Read(rawsalt)
197 if err != nil { 199 if err != nil {
198 return "", err 200 return "", err
199 } 201 }
200 202
201 s = hex.EncodeToString(rawsalt) 203 s = hex.EncodeToString(rawsalt)
202 return s, nil 204 return s, nil
203 } 205 }
204 206
205 // secretFunc returns byte slice of API secret keyword. 207 // secretFunc returns byte slice of API secret keyword.
206 func secretFunc(token *jwt.Token) (interface{}, error) { 208 func secretFunc(token *jwt.Token) (interface{}, error) {
207 return []byte(_secret), nil 209 return []byte(_secret), nil
208 } 210 }
209 211
default_values.go
1 package webutility 1 package webutility
2 2
3 // Int32ValueOrDefault ...
3 func Int32ValueOrDefault(val *interface{}) (def int32) { 4 func Int32ValueOrDefault(val *interface{}) (def int32) {
4 if *val != nil { 5 if *val != nil {
5 def = (*val).(int32) 6 def = (*val).(int32)
6 } 7 }
7 return def 8 return def
8 } 9 }
9 10
11 // Int64ValueOrDefault ...
10 func Int64ValueOrDefault(val *interface{}) (def int64) { 12 func Int64ValueOrDefault(val *interface{}) (def int64) {
11 if *val != nil { 13 if *val != nil {
12 def = (*val).(int64) 14 def = (*val).(int64)
13 } 15 }
14 return def 16 return def
15 } 17 }
16 18
19 // Uint32ValueOrDefault ...
17 func Uint32ValueOrDefault(val *interface{}) (def uint32) { 20 func Uint32ValueOrDefault(val *interface{}) (def uint32) {
18 if *val != nil { 21 if *val != nil {
19 def = (*val).(uint32) 22 def = (*val).(uint32)
20 } 23 }
21 return def 24 return def
22 } 25 }
23 26
27 // Uint64ValueOrDefault ...
24 func Uint64ValueOrDefault(val *interface{}) (def uint64) { 28 func Uint64ValueOrDefault(val *interface{}) (def uint64) {
25 if *val != nil { 29 if *val != nil {
26 def = (*val).(uint64) 30 def = (*val).(uint64)
27 } 31 }
28 return def 32 return def
29 } 33 }
30 34
35 // Float32ValueOrDefault ...
31 func Float32ValueOrDefault(val *interface{}) (def float32) { 36 func Float32ValueOrDefault(val *interface{}) (def float32) {
32 if *val != nil { 37 if *val != nil {
33 def = (*val).(float32) 38 def = (*val).(float32)
34 } 39 }
35 return def 40 return def
36 } 41 }
37 42
43 // Float64ValueOrDefault ...
38 func Float64ValueOrDefault(val *interface{}) (def float64) { 44 func Float64ValueOrDefault(val *interface{}) (def float64) {
39 if *val != nil { 45 if *val != nil {
40 return (*val).(float64) 46 return (*val).(float64)
41 } 47 }
42 return def 48 return def
43 } 49 }
44 50
51 // StringValueOrDefault ...
45 func StringValueOrDefault(val *interface{}) (def string) { 52 func StringValueOrDefault(val *interface{}) (def string) {
46 if *val != nil { 53 if *val != nil {
47 def = (*val).(string) 54 def = (*val).(string)
48 } 55 }
49 return def 56 return def
50 } 57 }
58
51 59
1 package webutility 1 package webutility
2 2
3 // TODO(markO): test test test test test (and open source?) 3 // TODO(markO): test test test test test (and open source?)
4 4
5 import ( 5 import (
6 "crypto/tls" 6 "crypto/tls"
7 "errors" 7 "errors"
8 "fmt" 8 "fmt"
9 "net/smtp" 9 "net/smtp"
10 "strings" 10 "strings"
11 ) 11 )
12 12
13 // Email ...
13 type Email struct { 14 type Email struct {
14 To []string 15 recipients []string
15 From string 16 sender string
16 Subject string 17 subject string
17 Body string 18 body string
18 19
19 config *EmailConfig 20 config *EmailConfig
20 } 21 }
21 22
23 // NewEmail ...
22 func NewEmail() *Email { 24 func NewEmail() *Email {
23 return &Email{ 25 return new(Email)
24 To: nil, 26 }
25 From: "", 27
26 Subject: "", 28 // EmailConfig ...
27 Body: "", 29 type EmailConfig struct {
28 config: nil, 30 ServerName string `json:"-"`
31 Identity string `json:"-"`
32 Username string `json:"username"`
33 Password string `json:"password"`
34 Host string `json:"host"`
35 Port int `json:"port"`
36 }
37
38 // NewEmailConfig ...
39 func NewEmailConfig(ident, uname, pword, host string, port int) *EmailConfig {
40 return &EmailConfig{
41 ServerName: host + fmt.Sprintf(":%d", port),
42 Identity: ident,
43 Username: uname,
44 Password: pword,
45 Host: host,
46 Port: port,
29 } 47 }
30 } 48 }
31 49
50 // Config ...
32 func (e *Email) Config(cfg *EmailConfig) { 51 func (e *Email) Config(cfg *EmailConfig) {
33 e.config = cfg 52 e.config = cfg
34 } 53 }
35 54
36 func (e *Email) SetFrom(from string) { 55 // Sender ...
37 e.From = from 56 func (e *Email) Sender(from string) {
57 e.sender = from
38 } 58 }
39 59
40 func (e *Email) SetTo(to []string) { 60 // AddRecipient ...
41 e.To = to 61 func (e *Email) AddRecipient(r string) {
62 e.recipients = append(e.recipients, r)
42 } 63 }
43 64
44 func (e *Email) SetSubject(sub string) { 65 // Subject ...
45 e.Subject = sub 66 func (e *Email) Subject(sub string) {
67 e.subject = sub
46 } 68 }
47 69
48 func (e *Email) SetBody(body string) { 70 func (e *Email) Write(body string) {
49 e.Body = body 71 e.body = body
50 } 72 }
51 73
52 func (e *Email) String() string { 74 func (e *Email) String() string {
53 var str strings.Builder 75 var str strings.Builder
54 76
55 str.WriteString("From:" + e.From + "\r\n") 77 str.WriteString("From:" + e.sender + "\r\n")
56 78
57 str.WriteString("To:") 79 str.WriteString("To:")
58 for i, _ := range e.To { 80 for i := range e.recipients {
59 if i > 0 { 81 if i > 0 {
60 str.WriteString(",") 82 str.WriteString(",")
61 } 83 }
62 str.WriteString(e.To[i]) 84 str.WriteString(e.recipients[i])
63 } 85 }
64 str.WriteString("\r\n") 86 str.WriteString("\r\n")
65 87
66 str.WriteString("Subject:" + e.Subject + "\r\n") 88 str.WriteString("Subject:" + e.subject + "\r\n")
67 89
68 // body 90 // body
69 str.WriteString("\r\n" + e.Body + "\r\n") 91 str.WriteString("\r\n" + e.body + "\r\n")
70 92
71 return str.String() 93 return str.String()
72 } 94 }
73 95
96 // Bytes ...
74 func (e *Email) Bytes() []byte { 97 func (e *Email) Bytes() []byte {
75 return []byte(e.String()) 98 return []byte(e.String())
76 } 99 }
77 100
78 func (email *Email) Send() error { 101 // Send ...
79 if email.config == nil { 102 func (e *Email) Send() error {
103 if e.config == nil {
80 return errors.New("email configuration not provided") 104 return errors.New("email configuration not provided")
81 } 105 }
82 conf := email.config 106 conf := e.config
83 107
84 c, err := smtp.Dial(conf.ServerName) 108 c, err := smtp.Dial(conf.ServerName)
85 if err != nil { 109 if err != nil {
86 return err 110 return err
87 } 111 }
88 defer c.Close() 112 defer c.Close()
89 113
90 // not sure if this is needed 114 /*
91 //if err = c.Hello(conf.ServerName); err != nil { 115 // not sure if this is needed
92 // return err 116 if err = c.Hello(conf.ServerName); err != nil {
93 //} 117 return err
118 }
119 */
94 120
95 if ok, _ := c.Extension("STARTTLS"); ok { 121 if ok, _ := c.Extension("STARTTLS"); ok {
96 // disable stupid tls check for self-signed certificates 122 // disable stupid tls check for self-signed certificates
97 config := &tls.Config{ 123 config := &tls.Config{
98 ServerName: conf.ServerName, 124 ServerName: conf.ServerName,
99 InsecureSkipVerify: true, 125 InsecureSkipVerify: true,
100 } 126 }
101 // for golang testing 127 /*
102 //if testHookStartTLS != nil { 128 // for golang testing
103 // testHookStartTLS(config) 129 if testHookStartTLS != nil {
104 //} 130 testHookStartTLS(config)
131 }
132 */
105 if err = c.StartTLS(config); err != nil { 133 if err = c.StartTLS(config); err != nil {
106 return err 134 return err
107 } 135 }
108 } 136 }
109 137
110 /* 138 /*
111 // don't know what to do with this 139 // don't know what to do with this
112 if a != nil && c.ext != nil { 140 if a != nil && c.ext != nil {
113 if _, ok := c.ext["AUTH"]; !ok { 141 if _, ok := c.ext["AUTH"]; !ok {
114 return errors.New("smtp: server doesn't support AUTH") 142 return errors.New("smtp: server doesn't support AUTH")
115 } 143 }
116 if err = c.Auth(a); err != nil { 144 if err = c.Auth(a); err != nil {
117 return err 145 return err
118 } 146 }
119 } 147 }
120 */ 148 */
121 149
122 // Set up authentication information. 150 // Set up authentication information.
123 auth := smtp.PlainAuth(conf.Identity, conf.Username, conf.Password, conf.Host) 151 auth := smtp.PlainAuth(conf.Identity, conf.Username, conf.Password, conf.Host)
124 if err = c.Auth(auth); err != nil { 152 if err = c.Auth(auth); err != nil {
125 return err 153 return err
126 } 154 }
127 155
128 if err = c.Mail(email.From); err != nil { 156 if err = c.Mail(e.sender); err != nil {
129 return err 157 return err
130 } 158 }
131 159
132 for _, addr := range email.To { 160 for _, addr := range e.recipients {
133 if err = c.Rcpt(addr); err != nil { 161 if err = c.Rcpt(addr); err != nil {
134 return err 162 return err
135 } 163 }
136 } 164 }
137 165
138 w, err := c.Data() 166 w, err := c.Data()
139 if err != nil { 167 if err != nil {
140 return err 168 return err
141 } 169 }
142 170
143 _, err = w.Write(email.Bytes()) 171 _, err = w.Write(e.Bytes())
144 if err != nil { 172 if err != nil {
145 return err 173 return err
146 } 174 }
147 175
148 err = w.Close() 176 err = w.Close()
149 if err != nil { 177 if err != nil {
150 return err 178 return err
151 } 179 }
152 180
153 return c.Quit() 181 return c.Quit()
154 } 182 }
155
1 package webutility 1 package webutility
2 2
3 import ( 3 import (
4 "fmt" 4 "fmt"
5 "net/http" 5 "net/http"
6 "net/url" 6 "net/url"
7 "strings" 7 "strings"
8 ) 8 )
9 9
10 // Filter ...
10 type Filter map[string][]string 11 type Filter map[string][]string
11 12
13 // Get ...
12 func (f Filter) Get(key string) (values []string, ok bool) { 14 func (f Filter) Get(key string) (values []string, ok bool) {
13 values, ok = f[key] 15 values, ok = f[key]
14 return values, ok 16 return values, ok
15 } 17 }
16 18
19 // Count ...
17 func (f Filter) Count() int { 20 func (f Filter) Count() int {
18 return len(f) 21 return len(f)
19 } 22 }
20 23
24 // Add ...
21 func (f Filter) Add(key, val string) { 25 func (f Filter) Add(key, val string) {
22 f[key] = append(f[key], val) 26 f[key] = append(f[key], val)
23 } 27 }
24 28
29 // ValueAt ...
25 func (f Filter) ValueAt(val string, index int) string { 30 func (f Filter) ValueAt(val string, index int) string {
26 if filter, ok := f[val]; ok { 31 if filter, ok := f[val]; ok {
27 if len(filter) > index { 32 if len(filter) > index {
28 return filter[index] 33 return filter[index]
29 } 34 }
30 } 35 }
31 36
32 return "" 37 return ""
33 } 38 }
34 39
35 func (fs Filter) validate(validFilters []string) (Filter, bool) { 40 func (f Filter) validate(validFilters []string) (Filter, bool) {
36 goodFilters := make(Filter) 41 goodFilters := make(Filter)
37 cnt, len := 0, 0 42 cnt, len := 0, 0
38 for f, _ := range fs { 43 for fi := range f {
39 len++ 44 len++
40 for _, v := range validFilters { 45 for _, v := range validFilters {
41 if f == v { 46 if fi == v {
42 cnt++ 47 cnt++
43 goodFilters[f] = fs[f] 48 goodFilters[fi] = f[fi]
44 } 49 }
45 } 50 }
46 } 51 }
47 52
48 result := true 53 result := true
49 if len > 0 && cnt == 0 { 54 if len > 0 && cnt == 0 {
50 // if no valid filters are found declare filtering request as invalid 55 // if no valid filters are found declare filtering request as invalid
51 result = false 56 result = false
52 } 57 }
53 58
54 return goodFilters, result 59 return goodFilters, result
55 } 60 }
56 61
57 // requires input in format: "param1::value1|param2::value2..." 62 // ParseFilters requires input in format: "param1::value1|param2::value2..."
58 func ParseFilters(req *http.Request, header string) (filters Filter) { 63 func ParseFilters(req *http.Request, header string) (filters Filter) {
59 q := req.FormValue(header) 64 q := req.FormValue(header)
60 q = strings.Trim(q, "\"") 65 q = strings.Trim(q, "\"")
61 kvp := strings.Split(q, "|") 66 kvp := strings.Split(q, "|")
62 filters = make(Filter, len(kvp)) 67 filters = make(Filter, len(kvp))
63 68
64 for i, _ := range kvp { 69 for i := range kvp {
65 kv := strings.Split(kvp[i], "::") 70 kv := strings.Split(kvp[i], "::")
66 if len(kv) == 2 { 71 if len(kv) == 2 {
67 key, _ := url.QueryUnescape(kv[0]) 72 key, _ := url.QueryUnescape(kv[0])
68 73
69 // get values (if more than 1) 74 // get values (if more than 1)
70 vals := strings.Split(kv[1], ",") 75 vals := strings.Split(kv[1], ",")
71 for _, v := range vals { 76 for _, v := range vals {
72 u, _ := url.QueryUnescape(v) 77 u, _ := url.QueryUnescape(v)
73 filters[key] = append(filters[key], u) 78 filters[key] = append(filters[key], u)
74 } 79 }
75 } 80 }
76 } 81 }
77 82
78 return filters 83 return filters
79 } 84 }
80 85
81 // TODO(marko): very dodgy, needs more robustness 86 // MakeFilterString is very dodgy, needs more robustness.
87 // TODO(marko)
82 func MakeFilterString(prefix string, filters Filter, validFilters []string) (res string, ok bool) { 88 func MakeFilterString(prefix string, filters Filter, validFilters []string) (res string, ok bool) {
83 if prefix != "" { 89 if prefix != "" {
84 prefix += "." 90 prefix += "."
85 } 91 }
86 92
87 if filters.Count() == 0 { 93 if filters.Count() == 0 {
88 return "", true 94 return "", true
89 } 95 }
90 96
91 filters, ok = filters.validate(validFilters) 97 filters, ok = filters.validate(validFilters)
92 if !ok { 98 if !ok {
93 return "", false 99 return "", false
94 } 100 }
95 101
96 first := true 102 first := true
97 for k, filter := range filters { 103 for k, filter := range filters {
98 symbol := "=" 104 symbol := "="
99 105
100 if first { 106 if first {
101 res += " where " 107 res += " where "
102 first = false 108 first = false
103 } else { 109 } else {
104 res += " and " 110 res += " and "
105 } 111 }
106 112
107 res += "(" 113 res += "("
108 for i, f := range filter { 114 for i, f := range filter {
109 if strings.HasPrefix(f, "<") || strings.HasPrefix(f, ">") || strings.HasPrefix(f, "!") { 115 if strings.HasPrefix(f, "<") || strings.HasPrefix(f, ">") || strings.HasPrefix(f, "!") {
110 symbol = string(f[0]) 116 symbol = string(f[0])
111 f = strings.TrimLeft(f, "<>!") 117 f = strings.TrimLeft(f, "<>!")
112 if strings.HasPrefix(f, "=") { 118 if strings.HasPrefix(f, "=") {
113 f = strings.TrimLeft(f, "=") 119 f = strings.TrimLeft(f, "=")
114 symbol += "=" 120 symbol += "="
115 } 121 }
116 } 122 }
117 123
118 res += fmt.Sprintf("%s%s %s '%s'", prefix, k, symbol, f) 124 res += fmt.Sprintf("%s%s %s '%s'", prefix, k, symbol, f)
119 125
120 if i < len(filter)-1 { 126 if i < len(filter)-1 {
121 res += " or " 127 res += " or "
122 } 128 }
123 } 129 }
124 res += ")" 130 res += ")"
125 } 131 }
126 132
127 return res, ok 133 return res, ok
128 } 134 }
129 135
1 package webutility 1 package webutility
2 2
3 import ( 3 import (
4 "fmt" 4 "fmt"
5 "time" 5 "time"
6 ) 6 )
7 7
8 // UnixToDate converts given Unix time to local time in format and returns result: 8 // UnixToDate converts given Unix time to local time in format and returns result:
9 // YYYY-MM-DD hh:mm:ss +zzzz UTC 9 // YYYY-MM-DD hh:mm:ss +zzzz UTC
10 func UnixToDate(unix int64) time.Time { 10 func UnixToDate(unix int64) time.Time {
11 return time.Unix(unix, 0) 11 return time.Unix(unix, 0)
12 } 12 }
13 13
14 // DateToUnix converts given date in Unix timestamp. 14 // DateToUnix converts given date in Unix timestamp.
15 func DateToUnix(date interface{}) int64 { 15 func DateToUnix(date interface{}) int64 {
16 if date != nil { 16 if date != nil {
17 t, ok := date.(time.Time) 17 t, ok := date.(time.Time)
18 if !ok { 18 if !ok {
19 return 0 19 return 0
20 } 20 }
21 return t.Unix() 21 return t.Unix()
22 22
23 } 23 }
24 return 0 24 return 0
25 } 25 }
26 26
27 // EqualQuotes encapsulates given string in SQL 'equal' statement and returns result. 27 // EqualQuotes encapsulates given string in SQL 'equal' statement and returns result.
28 // Example: "hello" -> " = 'hello'" 28 // Example: "hello" -> " = 'hello'"
29 func EqualQuotes(stmt string) string { 29 func EqualQuotes(stmt string) string {
30 if stmt != "" { 30 if stmt != "" {
31 stmt = fmt.Sprintf(" = '%s'", stmt) 31 stmt = fmt.Sprintf(" = '%s'", stmt)
32 } 32 }
33 return stmt 33 return stmt
34 } 34 }
35 35
36 // EqualString ...
36 func EqualString(stmt string) string { 37 func EqualString(stmt string) string {
37 if stmt != "" { 38 if stmt != "" {
38 stmt = fmt.Sprintf(" = %s", stmt) 39 stmt = fmt.Sprintf(" = %s", stmt)
39 } 40 }
40 return stmt 41 return stmt
41 } 42 }
42 43
43 // LikeQuotes encapsulates given string in SQL 'like' statement and returns result. 44 // LikeQuotes encapsulates given string in SQL 'like' statement and returns result.
44 // Example: "hello" -> " LIKE UPPER('%hello%')" 45 // Example: "hello" -> " LIKE UPPER('%hello%')"
45 func LikeQuotes(stmt string) string { 46 func LikeQuotes(stmt string) string {
46 if stmt != "" { 47 if stmt != "" {
47 stmt = fmt.Sprintf(" LIKE UPPER('%s%s%s')", "%", stmt, "%") 48 stmt = fmt.Sprintf(" LIKE UPPER('%s%s%s')", "%", stmt, "%")
48 } 49 }
49 return stmt 50 return stmt
50 } 51 }
52
51 53
1 package webutility 1 package webutility
2 2
3 import ( 3 import (
4 "encoding/json" 4 "encoding/json"
5 "fmt" 5 "fmt"
6 "net/http" 6 "net/http"
7 ) 7 )
8 8
9 // StatusRecorder ...
9 type StatusRecorder struct { 10 type StatusRecorder struct {
10 writer http.ResponseWriter 11 writer http.ResponseWriter
11 status int 12 status int
12 size int 13 size int
13 } 14 }
14 15
16 // NewStatusRecorder ...
15 func NewStatusRecorder(w http.ResponseWriter) *StatusRecorder { 17 func NewStatusRecorder(w http.ResponseWriter) *StatusRecorder {
16 return &StatusRecorder{ 18 return &StatusRecorder{
17 writer: w, 19 writer: w,
18 status: 0, 20 status: 0,
19 size: 0, 21 size: 0,
20 } 22 }
21 } 23 }
22 24
23 // http.ResponseWriter interface 25 // WriteHeader is a wrapper http.ResponseWriter interface
24 func (r *StatusRecorder) WriteHeader(code int) { 26 func (r *StatusRecorder) WriteHeader(code int) {
25 r.status = code 27 r.status = code
26 r.writer.WriteHeader(code) 28 r.writer.WriteHeader(code)
27 } 29 }
28 30
29 // http.ResponseWriter interface 31 // Write is a wrapper for http.ResponseWriter interface
30 func (r *StatusRecorder) Write(in []byte) (int, error) { 32 func (r *StatusRecorder) Write(in []byte) (int, error) {
31 r.size = len(in) 33 r.size = len(in)
32 return r.writer.Write(in) 34 return r.writer.Write(in)
33 } 35 }
34 36
35 // http.ResponseWriter interface 37 // Header is a wrapper for http.ResponseWriter interface
36 func (r *StatusRecorder) Header() http.Header { 38 func (r *StatusRecorder) Header() http.Header {
37 return r.writer.Header() 39 return r.writer.Header()
38 } 40 }
39 41
42 // Status ...
40 func (r *StatusRecorder) Status() int { 43 func (r *StatusRecorder) Status() int {
41 return r.status 44 return r.status
42 } 45 }
43 46
47 // Size ...
44 func (r *StatusRecorder) Size() int { 48 func (r *StatusRecorder) Size() int {
45 return r.size 49 return r.size
46 } 50 }
47 51
48 // NotFoundHandlerFunc writes HTTP error 404 to w. 52 // NotFoundHandlerFunc writes HTTP error 404 to w.
49 func NotFoundHandlerFunc(w http.ResponseWriter, req *http.Request) { 53 func NotFoundHandlerFunc(w http.ResponseWriter, req *http.Request) {
50 SetDefaultHeaders(w) 54 SetDefaultHeaders(w)
51 if req.Method == "OPTIONS" { 55 if req.Method == "OPTIONS" {
52 return 56 return
53 } 57 }
54 NotFound(w, req, fmt.Sprintf("Resource you requested was not found: %s", req.URL.String())) 58 NotFound(w, req, fmt.Sprintf("Resource you requested was not found: %s", req.URL.String()))
55 } 59 }
56 60
57 // SetDefaultHeaders set's default headers for an HTTP response. 61 // SetDefaultHeaders set's default headers for an HTTP response.
58 func SetDefaultHeaders(w http.ResponseWriter) { 62 func SetDefaultHeaders(w http.ResponseWriter) {
59 w.Header().Set("Access-Control-Allow-Origin", "*") 63 w.Header().Set("Access-Control-Allow-Origin", "*")
60 w.Header().Set("Access-Control-Allow-Methods", "POST, GET, PUT, DELETE, OPTIONS") 64 w.Header().Set("Access-Control-Allow-Methods", "POST, GET, PUT, DELETE, OPTIONS")
61 w.Header().Set("Access-Control-Allow-Headers", "Accept, Content-Type, Content-Length, Accept-Encoding, X-CSRF-Token, Authorization") 65 w.Header().Set("Access-Control-Allow-Headers", "Accept, Content-Type, Content-Length, Accept-Encoding, X-CSRF-Token, Authorization")
62 w.Header().Set("Content-Type", "application/json; charset=utf-8") 66 w.Header().Set("Content-Type", "application/json; charset=utf-8")
63 } 67 }
64 68
69 // GetLocale ...
65 func GetLocale(req *http.Request, dflt string) string { 70 func GetLocale(req *http.Request, dflt string) string {
66 loc := req.FormValue("locale") 71 loc := req.FormValue("locale")
67 if loc == "" { 72 if loc == "" {
68 return dflt 73 return dflt
69 } 74 }
70 return loc 75 return loc
71 } 76 }
72 77
73 // 2xx 78 // Success ...
74 func Success(w http.ResponseWriter, payload interface{}, code int) { 79 func Success(w http.ResponseWriter, payload interface{}, code int) {
75 w.WriteHeader(code) 80 w.WriteHeader(code)
76 if payload != nil { 81 if payload != nil {
77 json.NewEncoder(w).Encode(payload) 82 json.NewEncoder(w).Encode(payload)
78 } 83 }
79 } 84 }
80 85
81 // 200 86 // OK ...
82 func OK(w http.ResponseWriter, payload interface{}) { 87 func OK(w http.ResponseWriter, payload interface{}) {
83 Success(w, payload, http.StatusOK) 88 Success(w, payload, http.StatusOK)
84 } 89 }
85 90
86 // 201 91 // Created ...
87 func Created(w http.ResponseWriter, payload interface{}) { 92 func Created(w http.ResponseWriter, payload interface{}) {
88 Success(w, payload, http.StatusCreated) 93 Success(w, payload, http.StatusCreated)
89 } 94 }
90 95
91 type weberror struct { 96 type weberror struct {
92 Request string `json:"request"` 97 Request string `json:"request"`
93 Error string `json:"error"` 98 Error string `json:"error"`
94 } 99 }
95 100
96 // 4xx; 5xx 101 // Error ...
97 func Error(w http.ResponseWriter, r *http.Request, code int, err string) { 102 func Error(w http.ResponseWriter, r *http.Request, code int, err string) {
98 werr := weberror{Error: err, Request: r.Method + " " + r.RequestURI} 103 werr := weberror{Error: err, Request: r.Method + " " + r.RequestURI}
99 w.WriteHeader(code) 104 w.WriteHeader(code)
100 json.NewEncoder(w).Encode(werr) 105 json.NewEncoder(w).Encode(werr)
101 } 106 }
102 107
103 // 400 108 // BadRequest ...
104 func BadRequest(w http.ResponseWriter, r *http.Request, err string) { 109 func BadRequest(w http.ResponseWriter, r *http.Request, err string) {
105 Error(w, r, http.StatusBadRequest, err) 110 Error(w, r, http.StatusBadRequest, err)
106 } 111 }
107 112
108 // 401 113 // Unauthorized ...
109 func Unauthorized(w http.ResponseWriter, r *http.Request, err string) { 114 func Unauthorized(w http.ResponseWriter, r *http.Request, err string) {
110 Error(w, r, http.StatusUnauthorized, err) 115 Error(w, r, http.StatusUnauthorized, err)
111 } 116 }
112 117
113 // 403 118 // Forbidden ...
114 func Forbidden(w http.ResponseWriter, r *http.Request, err string) { 119 func Forbidden(w http.ResponseWriter, r *http.Request, err string) {
115 Error(w, r, http.StatusForbidden, err) 120 Error(w, r, http.StatusForbidden, err)
116 } 121 }
117 122
118 // 404 123 // NotFound ...
119 func NotFound(w http.ResponseWriter, r *http.Request, err string) { 124 func NotFound(w http.ResponseWriter, r *http.Request, err string) {
120 Error(w, r, http.StatusNotFound, err) 125 Error(w, r, http.StatusNotFound, err)
121 } 126 }
122 127
123 // 409 128 // Conflict ...
124 func Conflict(w http.ResponseWriter, r *http.Request, err string) { 129 func Conflict(w http.ResponseWriter, r *http.Request, err string) {
125 Error(w, r, http.StatusConflict, err) 130 Error(w, r, http.StatusConflict, err)
126 } 131 }
127 132
128 // 500 133 // InternalServerError ...
129 func InternalServerError(w http.ResponseWriter, r *http.Request, err string) { 134 func InternalServerError(w http.ResponseWriter, r *http.Request, err string) {
130 Error(w, r, http.StatusInternalServerError, err) 135 Error(w, r, http.StatusInternalServerError, err)
131 } 136 }
137
132 138
1 package webutility 1 package webutility
2 2
3 import ( 3 import (
4 "database/sql" 4 "database/sql"
5 "fmt" 5 "fmt"
6 ) 6 )
7 7
8 // ListOptions ...
8 type ListOptions struct { 9 type ListOptions struct {
9 GlobalFilter bool `json:"globalFilter"` 10 GlobalFilter bool `json:"globalFilter"`
10 LocalFilters bool `json:"localFilters"` 11 LocalFilters bool `json:"localFilters"`
11 RemoteFilters bool `json:"remoteFilters"` 12 RemoteFilters bool `json:"remoteFilters"`
12 Pagination bool `json:"pagination"` 13 Pagination bool `json:"pagination"`
13 PageSize uint32 `json:"pageSize"` 14 PageSize uint32 `json:"pageSize"`
14 Pivot bool `json:"pivot"` 15 Pivot bool `json:"pivot"`
15 Detail bool `json:"detail"` 16 Detail bool `json:"detail"`
16 Total bool `json:"total"` 17 Total bool `json:"total"`
17 } 18 }
18 19
20 // ListFilter ...
19 type ListFilter struct { 21 type ListFilter struct {
20 Position uint32 `json:"-"` 22 Position uint32 `json:"-"`
21 ObjectType string `json:"-"` 23 ObjectType string `json:"-"`
22 FiltersField string `json:"filtersField"` 24 FiltersField string `json:"filtersField"`
23 DefaultValues string `json:"defaultValues"` 25 DefaultValues string `json:"defaultValues"`
24 FiltersType string `json:"filtersType"` 26 FiltersType string `json:"filtersType"`
25 FiltersLabel string `json:"filtersLabel"` 27 FiltersLabel string `json:"filtersLabel"`
26 DropdownConfig Dropdown `json:"dropdownConfig"` 28 DropdownConfig Dropdown `json:"dropdownConfig"`
27 } 29 }
28 30
31 // Dropdown ...
29 type Dropdown struct { 32 type Dropdown struct {
30 ObjectType string `json:"objectType"` 33 ObjectType string `json:"objectType"`
31 FiltersField string `json:"filtersField"` 34 FiltersField string `json:"filtersField"`
32 IDField string `json:"idField"` 35 IDField string `json:"idField"`
33 LabelField string `json:"labelField"` 36 LabelField string `json:"labelField"`
34 } 37 }
35 38
39 // ListGraph ...
36 type ListGraph struct { 40 type ListGraph struct {
37 ObjectType string `json:"objectType"` 41 ObjectType string `json:"objectType"`
38 X string `json:"xField"` 42 X string `json:"xField"`
39 Y string `json:"yField"` 43 Y string `json:"yField"`
40 GroupField string `json:"groupField"` 44 GroupField string `json:"groupField"`
41 Label string `json:"label"` 45 Label string `json:"label"`
42 } 46 }
43 47
48 // ListActions ...
44 type ListActions struct { 49 type ListActions struct {
45 Create bool `json:"create"` 50 Create bool `json:"create"`
46 Update bool `json:"update"` 51 Update bool `json:"update"`
47 Delete bool `json:"delete"` 52 Delete bool `json:"delete"`
48 Export bool `json:"export"` 53 Export bool `json:"export"`
49 Print bool `json:"print"` 54 Print bool `json:"print"`
50 Graph bool `json:"graph"` 55 Graph bool `json:"graph"`
51 LiveGraph bool `json:"liveGraph"` 56 LiveGraph bool `json:"liveGraph"`
52 SaveFile bool `json:"saveFile"` 57 SaveFile bool `json:"saveFile"`
53 ShowFile bool `json:"showFile"` 58 ShowFile bool `json:"showFile"`
54 } 59 }
55 60
61 // ListNavNode ...
56 type ListNavNode struct { 62 type ListNavNode struct {
57 ObjectType string `json:"objectType"` 63 ObjectType string `json:"objectType"`
58 LabelField string `json:"label"` 64 LabelField string `json:"label"`
59 Icon string `json:"icon"` 65 Icon string `json:"icon"`
60 ParentObjectType string `json:"parentObjectType"` 66 ParentObjectType string `json:"parentObjectType"`
61 ParentIDField string `json:"parentIdField"` 67 ParentIDField string `json:"parentIdField"`
62 ParentFilterField string `json:"parentFilterField"` 68 ParentFilterField string `json:"parentFilterField"`
63 } 69 }
64 70
71 // ListParentNode ...
65 type ListParentNode struct { 72 type ListParentNode struct {
66 ObjectType string `json:"objectType"` 73 ObjectType string `json:"objectType"`
67 LabelField string `json:"labelField"` 74 LabelField string `json:"labelField"`
68 FilterField string `json:"filterField"` 75 FilterField string `json:"filterField"`
69 } 76 }
70 77
78 // ListPivot ...
71 type ListPivot struct { 79 type ListPivot struct {
72 ObjectType string `json:"objectType"` 80 ObjectType string `json:"objectType"`
73 GroupField string `json:"groupField"` 81 GroupField string `json:"groupField"`
74 DistinctField string `json:"distinctField"` 82 DistinctField string `json:"distinctField"`
75 Value string `json:"valueField"` 83 Value string `json:"valueField"`
76 } 84 }
77 85
86 // ListDetails ...
78 type ListDetails struct { 87 type ListDetails struct {
79 ObjectType string `json:"objectType"` 88 ObjectType string `json:"objectType"`
80 ParentObjectType string `json:"parentObjectType"` 89 ParentObjectType string `json:"parentObjectType"`
81 ParentFilterField string `json:"parentFilterField"` 90 ParentFilterField string `json:"parentFilterField"`
82 SingleDetail bool `json:"singleDetail"` 91 SingleDetail bool `json:"singleDetail"`
83 } 92 }
84 93
94 // ListLiveGraph ...
85 type ListLiveGraph struct { 95 type ListLiveGraph struct {
86 ObjectType string `json:"objectType"` 96 ObjectType string `json:"objectType"`
87 ValueFields string `json:"valueFields"` 97 ValueFields string `json:"valueFields"`
88 LabelFields string `json:"labelFields"` 98 LabelFields string `json:"labelFields"`
89 } 99 }
90 100
101 // ListConfig ...
91 type ListConfig struct { 102 type ListConfig struct {
92 ObjectType string `json:"objectType"` 103 ObjectType string `json:"objectType"`
93 Title string `json:"title"` 104 Title string `json:"title"`
94 LazyLoad bool `json:"lazyLoad"` 105 LazyLoad bool `json:"lazyLoad"`
95 InlineEdit bool `json:"inlineEdit"` 106 InlineEdit bool `json:"inlineEdit"`
96 Options ListOptions `json:"options"` 107 Options ListOptions `json:"options"`
97 Filters []ListFilter `json:"defaultFilters"` 108 Filters []ListFilter `json:"defaultFilters"`
98 Graphs []ListGraph `json:"graphs"` 109 Graphs []ListGraph `json:"graphs"`
99 Actions ListActions `json:"actions"` 110 Actions ListActions `json:"actions"`
100 Parent []ListParentNode `json:"parent"` 111 Parent []ListParentNode `json:"parent"`
101 Navigation []ListNavNode `json:"navigation"` 112 Navigation []ListNavNode `json:"navigation"`
102 Pivots []ListPivot `json:"pivots"` 113 Pivots []ListPivot `json:"pivots"`
103 Details ListDetails `json:"details"` 114 Details ListDetails `json:"details"`
104 LiveGraph ListLiveGraph `json:"liveGraphs"` 115 LiveGraph ListLiveGraph `json:"liveGraphs"`
105 } 116 }
106 117
107 // GetListConfig returns list configuration for the provided object type for the front-end application 118 // GetListConfig returns list configuration for the provided object type for the front-end application
108 // or an error if it fails. 119 // or an error if it fails.
109 func GetListConfig(db *sql.DB, objType string) (ListConfig, error) { 120 func GetListConfig(db *sql.DB, objType string) (ListConfig, error) {
110 list := NewListConfig(objType) 121 list := NewListConfig(objType)
111 122
112 err := list.setParams(db, objType) 123 err := list.setParams(db, objType)
113 err = list.SetNavigation(db, objType) 124 err = list.SetNavigation(db, objType)
114 err = list.SetActions(db, objType) 125 err = list.SetActions(db, objType)
115 err = list.SetFilters(db, objType) 126 err = list.SetFilters(db, objType)
116 err = list.SetOptions(db, objType) 127 err = list.SetOptions(db, objType)
117 err = list.SetParent(db, objType) 128 err = list.SetParent(db, objType)
118 err = list.SetPivot(db, objType) 129 err = list.SetPivot(db, objType)
119 err = list.SetGraph(db, objType) 130 err = list.SetGraph(db, objType)
120 err = list.SetDetails(db, objType) 131 err = list.SetDetails(db, objType)
121 err = list.SetLiveGraph(db, objType) 132 err = list.SetLiveGraph(db, objType)
122 133
123 if err != nil { 134 if err != nil {
124 return list, err 135 return list, err
125 } 136 }
126 137
127 return list, nil 138 return list, nil
128 } 139 }
129 140
130 // GetListConfigObjectIDField takes in database connection and an object type and it returns the 141 // GetListConfigObjectIDField takes in database connection and an object type and it returns the
131 // ID field name for the provided object type. 142 // ID field name for the provided object type.
132 func GetListConfigObjectIDField(db *sql.DB, otype string) string { 143 func GetListConfigObjectIDField(db *sql.DB, otype string) string {
133 var resp string 144 var resp string
134 145
135 rows, err := db.Query(`SELECT 146 rows, err := db.Query(`SELECT
136 ID_FIELD 147 ID_FIELD
137 FROM LIST_CONFIG_ID_FIELD 148 FROM LIST_CONFIG_ID_FIELD
138 WHERE OBJECT_TYPE = ` + fmt.Sprintf("'%s'", otype)) 149 WHERE OBJECT_TYPE = ` + fmt.Sprintf("'%s'", otype))
139 if err != nil { 150 if err != nil {
140 return "" 151 return ""
141 } 152 }
142 defer rows.Close() 153 defer rows.Close()
143 154
144 if rows.Next() { 155 if rows.Next() {
145 rows.Scan(&resp) 156 rows.Scan(&resp)
146 } 157 }
147 158
148 if rows.Err() != nil { 159 if rows.Err() != nil {
149 return "" 160 return ""
150 } 161 }
151 162
152 return resp 163 return resp
153 } 164 }
154 165
155 // newDefaultList returns default configuration for the provided object type. 166 // NewListConfig returns default configuration for the provided object type.
156 func NewListConfig(objType string) ListConfig { 167 func NewListConfig(objType string) ListConfig {
157 list := ListConfig{ 168 list := ListConfig{
158 ObjectType: objType, 169 ObjectType: objType,
159 Title: objType, 170 Title: objType,
160 LazyLoad: false, 171 LazyLoad: false,
161 Options: ListOptions{ 172 Options: ListOptions{
162 GlobalFilter: true, 173 GlobalFilter: true,
163 LocalFilters: true, 174 LocalFilters: true,
164 RemoteFilters: false, 175 RemoteFilters: false,
165 Pagination: true, 176 Pagination: true,
166 PageSize: 20, 177 PageSize: 20,
167 }, 178 },
168 Filters: nil, 179 Filters: nil,
169 Actions: ListActions{ 180 Actions: ListActions{
170 Create: false, 181 Create: false,
171 Update: false, 182 Update: false,
172 Delete: false, 183 Delete: false,
173 Export: false, 184 Export: false,
174 Print: false, 185 Print: false,
175 Graph: false, 186 Graph: false,
176 LiveGraph: false, 187 LiveGraph: false,
177 }, 188 },
178 Parent: nil, 189 Parent: nil,
179 Navigation: nil, 190 Navigation: nil,
180 } 191 }
181 192
182 return list 193 return list
183 } 194 }
184 195
185 // setListParams sets the default parameters of the provided configuration list for the provided object type. 196 // setParams sets the default parameters of the provided configuration list for the provided object type.
186 func (list *ListConfig) setParams(db *sql.DB, objType string) error { 197 func (list *ListConfig) setParams(db *sql.DB, objType string) error {
187 rows, err := db.Query(`SELECT 198 rows, err := db.Query(`SELECT
188 OBJECT_TYPE, 199 OBJECT_TYPE,
189 TITLE, 200 TITLE,
190 LAZY_LOAD, 201 LAZY_LOAD,
191 INLINE_EDIT 202 INLINE_EDIT
192 FROM LIST_CONFIG 203 FROM LIST_CONFIG
193 WHERE OBJECT_TYPE = ` + fmt.Sprintf("'%s'", objType)) 204 WHERE OBJECT_TYPE = ` + fmt.Sprintf("'%s'", objType))
194 if err != nil { 205 if err != nil {
195 return err 206 return err
196 } 207 }
197 defer rows.Close() 208 defer rows.Close()
198 if rows.Next() { 209 if rows.Next() {
199 otype, title := "", "" 210 otype, title := "", ""
200 lazyLoad, inlineEdit := 0, 0 211 lazyLoad, inlineEdit := 0, 0
201 rows.Scan(&otype, &title, &lazyLoad, &inlineEdit) 212 rows.Scan(&otype, &title, &lazyLoad, &inlineEdit)
202 213
203 if otype != "" { 214 if otype != "" {
204 list.ObjectType = otype 215 list.ObjectType = otype
205 } 216 }
206 if title != "" { 217 if title != "" {
207 list.Title = title 218 list.Title = title
208 } 219 }
209 list.LazyLoad = lazyLoad != 0 220 list.LazyLoad = lazyLoad != 0
210 list.InlineEdit = inlineEdit != 0 221 list.InlineEdit = inlineEdit != 0
211 } 222 }
212 if rows.Err() != nil { 223 if rows.Err() != nil {
213 return rows.Err() 224 return rows.Err()
214 } 225 }
215 return nil 226 return nil
216 } 227 }
217 228
218 // ListNavigation returns list navigation node slice for the provided objectType. 229 // SetNavigation returns set's navigation nodes for listObjType object type.
219 func (list *ListConfig) SetNavigation(db *sql.DB, listObjType string) error { 230 func (list *ListConfig) SetNavigation(db *sql.DB, listObjType string) error {
220 list.Navigation = make([]ListNavNode, 0) 231 list.Navigation = make([]ListNavNode, 0)
221 rows, err := db.Query(`SELECT 232 rows, err := db.Query(`SELECT
222 a.OBJECT_TYPE, 233 a.OBJECT_TYPE,
223 a.PARENT_OBJECT_TYPE, 234 a.PARENT_OBJECT_TYPE,
224 a.LABEL, 235 a.LABEL,
225 a.ICON, 236 a.ICON,
226 a.PARENT_FILTER_FIELD, 237 a.PARENT_FILTER_FIELD,
227 b.PARENT_ID_FIELD 238 b.PARENT_ID_FIELD
228 FROM LIST_CONFIG_NAVIGATION b 239 FROM LIST_CONFIG_NAVIGATION b
229 JOIN LIST_CONFIG_CHILD a ON b.PARENT_CHILD_ID = a.PARENT_CHILD_ID 240 JOIN LIST_CONFIG_CHILD a ON b.PARENT_CHILD_ID = a.PARENT_CHILD_ID
230 WHERE b.LIST_OBJECT_TYPE = ` + fmt.Sprintf("'%s'", listObjType) + 241 WHERE b.LIST_OBJECT_TYPE = ` + fmt.Sprintf("'%s'", listObjType) +
231 ` ORDER BY b.RB ASC`) 242 ` ORDER BY b.RB ASC`)
232 if err != nil { 243 if err != nil {
233 return err 244 return err
234 } 245 }
235 defer rows.Close() 246 defer rows.Close()
236 247
237 var node ListNavNode 248 var node ListNavNode
238 for rows.Next() { 249 for rows.Next() {
239 rows.Scan(&node.ObjectType, &node.ParentObjectType, &node.LabelField, &node.Icon, 250 rows.Scan(&node.ObjectType, &node.ParentObjectType, &node.LabelField, &node.Icon,
240 &node.ParentFilterField, &node.ParentIDField) 251 &node.ParentFilterField, &node.ParentIDField)
241 list.Navigation = append(list.Navigation, node) 252 list.Navigation = append(list.Navigation, node)
242 } 253 }
243 if rows.Err() != nil { 254 if rows.Err() != nil {
244 return rows.Err() 255 return rows.Err()
245 } 256 }
246 257
247 return nil 258 return nil
248 } 259 }
249 260
250 // getListActions returns list actions for the provided object type. 261 // SetActions sets list's actions based for objType object type.
251 func (list *ListConfig) SetActions(db *sql.DB, objType string) error { 262 func (list *ListConfig) SetActions(db *sql.DB, objType string) error {
252 rows, err := db.Query(`SELECT 263 rows, err := db.Query(`SELECT
253 ACTION_CREATE, 264 ACTION_CREATE,
254 ACTION_UPDATE, 265 ACTION_UPDATE,
255 ACTION_DELETE, 266 ACTION_DELETE,
256 ACTION_EXPORT, 267 ACTION_EXPORT,
257 ACTION_PRINT, 268 ACTION_PRINT,
258 ACTION_GRAPH, 269 ACTION_GRAPH,
259 ACTION_LIVE_GRAPH, 270 ACTION_LIVE_GRAPH,
260 ACTION_SAVE_FILE, 271 ACTION_SAVE_FILE,
261 ACTION_SHOW_FILE 272 ACTION_SHOW_FILE
262 FROM LIST_CONFIG 273 FROM LIST_CONFIG
263 WHERE OBJECT_TYPE = ` + fmt.Sprintf("'%s'", objType)) 274 WHERE OBJECT_TYPE = ` + fmt.Sprintf("'%s'", objType))
264 if err != nil { 275 if err != nil {
265 return err 276 return err
266 } 277 }
267 defer rows.Close() 278 defer rows.Close()
268 279
269 var create, update, delete, export, print, graph, liveGraph, saveFile, showFile uint32 280 var create, update, delete, export, print, graph, liveGraph, saveFile, showFile uint32
270 if rows.Next() { 281 if rows.Next() {
271 rows.Scan(&create, &update, &delete, &export, &print, &graph, &liveGraph, &saveFile, &showFile) 282 rows.Scan(&create, &update, &delete, &export, &print, &graph, &liveGraph, &saveFile, &showFile)
272 list.Actions.Create = create != 0 283 list.Actions.Create = create != 0
273 list.Actions.Update = update != 0 284 list.Actions.Update = update != 0
274 list.Actions.Delete = delete != 0 285 list.Actions.Delete = delete != 0
275 list.Actions.Export = export != 0 286 list.Actions.Export = export != 0
276 list.Actions.Print = print != 0 287 list.Actions.Print = print != 0
277 list.Actions.Graph = graph != 0 288 list.Actions.Graph = graph != 0
278 list.Actions.LiveGraph = liveGraph != 0 289 list.Actions.LiveGraph = liveGraph != 0
279 list.Actions.SaveFile = saveFile != 0 290 list.Actions.SaveFile = saveFile != 0
280 list.Actions.ShowFile = showFile != 0 291 list.Actions.ShowFile = showFile != 0
281 } 292 }
282 if rows.Err() != nil { 293 if rows.Err() != nil {
283 return rows.Err() 294 return rows.Err()
284 } 295 }
285 296
286 return nil 297 return nil
287 } 298 }
288 299
289 // getListFiters returns list filter slice for the provided object type. 300 // SetFilters ...
290 func (list *ListConfig) SetFilters(db *sql.DB, objType string) error { 301 func (list *ListConfig) SetFilters(db *sql.DB, objType string) error {
291 list.Filters = make([]ListFilter, 0) 302 list.Filters = make([]ListFilter, 0)
292 filtersFields, err := list.GetFilterFieldsAndPosition(db, objType) 303 filtersFields, err := list.GetFilterFieldsAndPosition(db, objType)
293 if err != nil { 304 if err != nil {
294 return err 305 return err
295 } 306 }
296 for field, pos := range filtersFields { 307 for field, pos := range filtersFields {
297 filters, _ := list.GetFiltersByFilterField(db, field) 308 filters, _ := list.getFiltersByFilterField(db, field)
298 for _, filter := range filters { 309 for _, filter := range filters {
299 var f ListFilter 310 var f ListFilter
300 f.Position = pos 311 f.Position = pos
301 f.ObjectType = objType 312 f.ObjectType = objType
302 f.FiltersField = field 313 f.FiltersField = field
303 f.DefaultValues = filter.DefaultValues 314 f.DefaultValues = filter.DefaultValues
304 f.FiltersLabel = filter.Label 315 f.FiltersLabel = filter.Label
305 f.FiltersType = filter.Type 316 f.FiltersType = filter.Type
306 if filter.Type == "dropdown" { 317 if filter.Type == "dropdown" {
307 err := f.SetDropdownConfig(db, field) 318 err := f.SetDropdownConfig(db, field)
308 if err != nil { 319 if err != nil {
309 return err 320 return err
310 } 321 }
311 } 322 }
312 list.Filters = append(list.Filters, f) 323 list.Filters = append(list.Filters, f)
313 } 324 }
314 } 325 }
315 326
316 list.sortFilters() 327 list.sortFilters()
317 328
318 return nil 329 return nil
319 } 330 }
320 331
321 // getFilterFieldsAndPosition returns a map of filter fields and their respective position in the menu. 332 // GetFilterFieldsAndPosition returns a map of filter fields and their respective position in the menu.
322 func (list *ListConfig) GetFilterFieldsAndPosition(db *sql.DB, objType string) (map[string]uint32, error) { 333 func (list *ListConfig) GetFilterFieldsAndPosition(db *sql.DB, objType string) (map[string]uint32, error) {
323 filtersField := make(map[string]uint32, 0) 334 filtersField := make(map[string]uint32, 0)
324 rows, err := db.Query(`SELECT 335 rows, err := db.Query(`SELECT
325 FILTERS_FIELD, 336 FILTERS_FIELD,
326 RB 337 RB
327 FROM LIST_CONFIG_FILTERS 338 FROM LIST_CONFIG_FILTERS
328 WHERE OBJECT_TYPE = ` + fmt.Sprintf("'%s'", objType)) 339 WHERE OBJECT_TYPE = ` + fmt.Sprintf("'%s'", objType))
329 if err != nil { 340 if err != nil {
330 return nil, err 341 return nil, err
331 } 342 }
332 defer rows.Close() 343 defer rows.Close()
333 344
334 for rows.Next() { 345 for rows.Next() {
335 var field string 346 var field string
336 var rb uint32 347 var rb uint32
337 rows.Scan(&field, &rb) 348 rows.Scan(&field, &rb)
338 filtersField[field] = rb 349 filtersField[field] = rb
339 } 350 }
340 if rows.Err() != nil { 351 if rows.Err() != nil {
341 return nil, rows.Err() 352 return nil, rows.Err()
342 } 353 }
343 return filtersField, nil 354 return filtersField, nil
344 } 355 }
345 356
346 type _filter struct { 357 type _filter struct {
347 DefaultValues string 358 DefaultValues string
348 Label string 359 Label string
349 Type string 360 Type string
350 } 361 }
351 362
352 // getFiltersByFilterField returns filter slice for the provided filter field. 363 // getFiltersByFilterField ...
353 func (list *ListConfig) GetFiltersByFilterField(db *sql.DB, filtersField string) ([]_filter, error) { 364 func (list *ListConfig) getFiltersByFilterField(db *sql.DB, filtersField string) ([]_filter, error) {
354 resp := make([]_filter, 0) 365 resp := make([]_filter, 0)
355 rows, err := db.Query(`SELECT 366 rows, err := db.Query(`SELECT
356 FILTERS_TYPE, 367 FILTERS_TYPE,
357 FILTERS_LABEL, 368 FILTERS_LABEL,
358 DEFAULT_VALUES 369 DEFAULT_VALUES
359 FROM LIST_FILTERS_FIELD 370 FROM LIST_FILTERS_FIELD
360 WHERE FILTERS_FIELD = ` + fmt.Sprintf("'%s'", filtersField)) 371 WHERE FILTERS_FIELD = ` + fmt.Sprintf("'%s'", filtersField))
361 if err != nil { 372 if err != nil {
362 return resp, err 373 return resp, err
363 } 374 }
364 defer rows.Close() 375 defer rows.Close()
365 376
366 var f _filter 377 var f _filter
367 for rows.Next() { 378 for rows.Next() {
368 rows.Scan(&f.Type, &f.Label, &f.DefaultValues) 379 rows.Scan(&f.Type, &f.Label, &f.DefaultValues)
369 resp = append(resp, f) 380 resp = append(resp, f)
370 } 381 }
371 if rows.Err() != nil { 382 if rows.Err() != nil {
372 return resp, rows.Err() 383 return resp, rows.Err()
373 } 384 }
374 return resp, nil 385 return resp, nil
375 } 386 }
376 387
377 // getFilterDropdownConfig returns dropdown menu for the provided filter field. 388 // SetDropdownConfig ...
378 func (f *ListFilter) SetDropdownConfig(db *sql.DB, filtersField string) error { 389 func (f *ListFilter) SetDropdownConfig(db *sql.DB, filtersField string) error {
379 var resp Dropdown 390 var resp Dropdown
380 rows, err := db.Query(`SELECT 391 rows, err := db.Query(`SELECT
381 FILTERS_FIELD, 392 FILTERS_FIELD,
382 OBJECT_TYPE, 393 OBJECT_TYPE,
383 ID_FIELD, 394 ID_FIELD,
384 LABEL_FIELD 395 LABEL_FIELD
385 FROM LIST_DROPDOWN_FILTER 396 FROM LIST_DROPDOWN_FILTER
386 WHERE FILTERS_FIELD = ` + fmt.Sprintf("'%s'", filtersField)) 397 WHERE FILTERS_FIELD = ` + fmt.Sprintf("'%s'", filtersField))
387 if err != nil { 398 if err != nil {
388 return err 399 return err
389 } 400 }
390 defer rows.Close() 401 defer rows.Close()
391 if rows.Next() { 402 if rows.Next() {
392 rows.Scan(&resp.FiltersField, &resp.ObjectType, &resp.IDField, &resp.LabelField) 403 rows.Scan(&resp.FiltersField, &resp.ObjectType, &resp.IDField, &resp.LabelField)
393 } 404 }
394 if rows.Err() != nil { 405 if rows.Err() != nil {
395 return rows.Err() 406 return rows.Err()
396 } 407 }
397 408
398 f.DropdownConfig = resp 409 f.DropdownConfig = resp
399 410
400 return nil 411 return nil
401 } 412 }
402 413
403 // sortFilters bubble sorts provided filters slice by position field. 414 // sortFilters bubble sorts provided filters slice by position field.
404 func (list *ListConfig) sortFilters() { 415 func (list *ListConfig) sortFilters() {
405 done := false 416 done := false
406 var temp ListFilter 417 var temp ListFilter
407 for !done { 418 for !done {
408 done = true 419 done = true
409 for i := 0; i < len(list.Filters)-1; i++ { 420 for i := 0; i < len(list.Filters)-1; i++ {
410 if list.Filters[i].Position > list.Filters[i+1].Position { 421 if list.Filters[i].Position > list.Filters[i+1].Position {
411 done = false 422 done = false
412 temp = list.Filters[i] 423 temp = list.Filters[i]
413 list.Filters[i] = list.Filters[i+1] 424 list.Filters[i] = list.Filters[i+1]
414 list.Filters[i+1] = temp 425 list.Filters[i+1] = temp
415 } 426 }
416 } 427 }
417 } 428 }
418 } 429 }
419 430
420 // getListGraph return list graph slice for the provided object type. 431 // SetGraph ...
421 func (list *ListConfig) SetGraph(db *sql.DB, objType string) error { 432 func (list *ListConfig) SetGraph(db *sql.DB, objType string) error {
422 list.Graphs = make([]ListGraph, 0) 433 list.Graphs = make([]ListGraph, 0)
423 rows, err := db.Query(`SELECT 434 rows, err := db.Query(`SELECT
424 OBJECT_TYPE, 435 OBJECT_TYPE,
425 X_FIELD, 436 X_FIELD,
426 Y_FIELD, 437 Y_FIELD,
427 GROUP_FIELD, 438 GROUP_FIELD,
428 LABEL 439 LABEL
429 FROM LIST_GRAPHS 440 FROM LIST_GRAPHS
430 WHERE OBJECT_TYPE = ` + fmt.Sprintf("'%s'", objType)) 441 WHERE OBJECT_TYPE = ` + fmt.Sprintf("'%s'", objType))
431 if err != nil { 442 if err != nil {
432 return err 443 return err
433 } 444 }
434 defer rows.Close() 445 defer rows.Close()
435 446
436 var lg ListGraph 447 var lg ListGraph
437 for rows.Next() { 448 for rows.Next() {
438 rows.Scan(&lg.ObjectType, &lg.X, &lg.Y, &lg.GroupField, &lg.Label) 449 rows.Scan(&lg.ObjectType, &lg.X, &lg.Y, &lg.GroupField, &lg.Label)
439 list.Graphs = append(list.Graphs, lg) 450 list.Graphs = append(list.Graphs, lg)
440 } 451 }
441 if rows.Err() != nil { 452 if rows.Err() != nil {
442 return rows.Err() 453 return rows.Err()
443 } 454 }
444 455
445 return nil 456 return nil
446 } 457 }
447 458
448 // getListOptions returns list options for the provided object type. 459 // SetOptions ...
449 func (list *ListConfig) SetOptions(db *sql.DB, objType string) error { 460 func (list *ListConfig) SetOptions(db *sql.DB, objType string) error {
450 rows, err := db.Query(`SELECT 461 rows, err := db.Query(`SELECT
451 GLOBAL_FILTER, 462 GLOBAL_FILTER,
452 LOCAL_FILTER, 463 LOCAL_FILTER,
453 REMOTE_FILTER, 464 REMOTE_FILTER,
454 PAGINATION, 465 PAGINATION,
455 PAGE_SIZE, 466 PAGE_SIZE,
456 PIVOT, 467 PIVOT,
457 DETAIL, 468 DETAIL,
458 TOTAL 469 TOTAL
459 FROM LIST_CONFIG 470 FROM LIST_CONFIG
460 WHERE OBJECT_TYPE = ` + fmt.Sprintf("'%s'", objType)) 471 WHERE OBJECT_TYPE = ` + fmt.Sprintf("'%s'", objType))
461 if err != nil { 472 if err != nil {
462 return err 473 return err
463 } 474 }
464 defer rows.Close() 475 defer rows.Close()
465 476
466 if rows.Next() { 477 if rows.Next() {
467 var gfilter, lfilters, rfilters, pagination, pageSize, pivot, detail, total uint32 478 var gfilter, lfilters, rfilters, pagination, pageSize, pivot, detail, total uint32
468 rows.Scan(&gfilter, &lfilters, &rfilters, &pagination, &pageSize, &pivot, &detail, &total) 479 rows.Scan(&gfilter, &lfilters, &rfilters, &pagination, &pageSize, &pivot, &detail, &total)
469 list.Options.GlobalFilter = gfilter != 0 480 list.Options.GlobalFilter = gfilter != 0
470 list.Options.LocalFilters = lfilters != 0 481 list.Options.LocalFilters = lfilters != 0
471 list.Options.RemoteFilters = rfilters != 0 482 list.Options.RemoteFilters = rfilters != 0
472 list.Options.Pagination = pagination != 0 483 list.Options.Pagination = pagination != 0
473 list.Options.PageSize = pageSize 484 list.Options.PageSize = pageSize
474 list.Options.Pivot = pivot != 0 485 list.Options.Pivot = pivot != 0
475 list.Options.Detail = detail != 0 486 list.Options.Detail = detail != 0
476 list.Options.Total = total != 0 487 list.Options.Total = total != 0
477 } 488 }
478 if rows.Err() != nil { 489 if rows.Err() != nil {
479 return rows.Err() 490 return rows.Err()
480 } 491 }
481 492
482 return nil 493 return nil
483 } 494 }
484 495
485 // getListParent returns list parent node slice for the provided object type. 496 // SetParent ...
486 func (list *ListConfig) SetParent(db *sql.DB, objType string) error { 497 func (list *ListConfig) SetParent(db *sql.DB, objType string) error {
487 list.Parent = make([]ListParentNode, 0) 498 list.Parent = make([]ListParentNode, 0)
488 rows, err := db.Query(`SELECT 499 rows, err := db.Query(`SELECT
489 PARENT_OBJECT_TYPE, 500 PARENT_OBJECT_TYPE,
490 PARENT_LABEL_FIELD, 501 PARENT_LABEL_FIELD,
491 PARENT_FILTER_FIELD 502 PARENT_FILTER_FIELD
492 FROM LIST_CONFIG_CHILD 503 FROM LIST_CONFIG_CHILD
493 WHERE OBJECT_TYPE = ` + fmt.Sprintf("'%s'", objType)) 504 WHERE OBJECT_TYPE = ` + fmt.Sprintf("'%s'", objType))
494 if err != nil { 505 if err != nil {
495 return err 506 return err
496 } 507 }
497 defer rows.Close() 508 defer rows.Close()
498 509
499 var pnode ListParentNode 510 var pnode ListParentNode
500 for rows.Next() { 511 for rows.Next() {
501 rows.Scan(&pnode.ObjectType, &pnode.LabelField, &pnode.FilterField) 512 rows.Scan(&pnode.ObjectType, &pnode.LabelField, &pnode.FilterField)
502 list.Parent = append(list.Parent, pnode) 513 list.Parent = append(list.Parent, pnode)
503 } 514 }
504 if rows.Err() != nil { 515 if rows.Err() != nil {
505 return rows.Err() 516 return rows.Err()
506 } 517 }
507 518
508 return nil 519 return nil
509 } 520 }
510 521
511 // getListPivot list pivot slice for the provided object type. 522 // SetPivot ...
512 func (list *ListConfig) SetPivot(db *sql.DB, objType string) error { 523 func (list *ListConfig) SetPivot(db *sql.DB, objType string) error {
513 list.Pivots = make([]ListPivot, 0) 524 list.Pivots = make([]ListPivot, 0)
514 rows, err := db.Query(`SELECT 525 rows, err := db.Query(`SELECT
515 OBJECT_TYPE, 526 OBJECT_TYPE,
516 GROUP_FIELD, 527 GROUP_FIELD,
517 DISTINCT_FIELD, 528 DISTINCT_FIELD,
518 VALUE_FIELD 529 VALUE_FIELD
519 FROM LIST_PIVOTS 530 FROM LIST_PIVOTS
520 WHERE OBJECT_TYPE = ` + fmt.Sprintf("'%s'", objType)) 531 WHERE OBJECT_TYPE = ` + fmt.Sprintf("'%s'", objType))
521 if err != nil { 532 if err != nil {
522 return err 533 return err
523 } 534 }
524 defer rows.Close() 535 defer rows.Close()
525 536
526 var p ListPivot 537 var p ListPivot
527 for rows.Next() { 538 for rows.Next() {
528 rows.Scan(&p.ObjectType, &p.GroupField, &p.DistinctField, &p.Value) 539 rows.Scan(&p.ObjectType, &p.GroupField, &p.DistinctField, &p.Value)
529 list.Pivots = append(list.Pivots, p) 540 list.Pivots = append(list.Pivots, p)
530 } 541 }
531 if rows.Err() != nil { 542 if rows.Err() != nil {
532 return rows.Err() 543 return rows.Err()
533 } 544 }
534 545
535 return nil 546 return nil
536 } 547 }
537 548
538 // getListDetails returns list details for the provided object type. 549 // SetDetails ...
539 func (list *ListConfig) SetDetails(db *sql.DB, objType string) error { 550 func (list *ListConfig) SetDetails(db *sql.DB, objType string) error {
540 var resp ListDetails 551 var resp ListDetails
541 rows, err := db.Query(`SELECT 552 rows, err := db.Query(`SELECT
542 OBJECT_TYPE, 553 OBJECT_TYPE,
543 PARENT_OBJECT_TYPE, 554 PARENT_OBJECT_TYPE,
544 PARENT_FILTER_FIELD, 555 PARENT_FILTER_FIELD,
545 SINGLE_DETAIL 556 SINGLE_DETAIL
546 FROM LIST_CONFIG_DETAIL 557 FROM LIST_CONFIG_DETAIL
547 WHERE PARENT_OBJECT_TYPE = ` + fmt.Sprintf("'%s'", objType)) 558 WHERE PARENT_OBJECT_TYPE = ` + fmt.Sprintf("'%s'", objType))
548 if err != nil { 559 if err != nil {
549 return err 560 return err
550 } 561 }
551 defer rows.Close() 562 defer rows.Close()
552 if rows.Next() { 563 if rows.Next() {
553 var singleDetail uint32 564 var singleDetail uint32
554 rows.Scan(&resp.ObjectType, &resp.ParentObjectType, &resp.ParentFilterField, &singleDetail) 565 rows.Scan(&resp.ObjectType, &resp.ParentObjectType, &resp.ParentFilterField, &singleDetail)
555 resp.SingleDetail = singleDetail != 0 566 resp.SingleDetail = singleDetail != 0
556 } 567 }
557 if rows.Err() != nil { 568 if rows.Err() != nil {
558 return rows.Err() 569 return rows.Err()
559 } 570 }
560 571
561 list.Details = resp 572 list.Details = resp
562 573
563 return nil 574 return nil
564 } 575 }
565 576
566 // getListLiveGraph returns live graph for the provided object type. 577 // SetLiveGraph ...
567 func (list *ListConfig) SetLiveGraph(db *sql.DB, objType string) error { 578 func (list *ListConfig) SetLiveGraph(db *sql.DB, objType string) error {
568 var resp ListLiveGraph 579 var resp ListLiveGraph
569 rows, err := db.Query(`SELECT 580 rows, err := db.Query(`SELECT
570 OBJECT_TYPE, 581 OBJECT_TYPE,
571 VALUE_FIELDS, 582 VALUE_FIELDS,
572 LABEL_FIELDS 583 LABEL_FIELDS
573 FROM LIST_LIVE_GRAPH 584 FROM LIST_LIVE_GRAPH
574 WHERE OBJECT_TYPE = ` + fmt.Sprintf("'%s'", objType)) 585 WHERE OBJECT_TYPE = ` + fmt.Sprintf("'%s'", objType))
575 if err != nil { 586 if err != nil {
576 return err 587 return err
577 } 588 }
578 defer rows.Close() 589 defer rows.Close()
579 if rows.Next() { 590 if rows.Next() {
580 rows.Scan(&resp.ObjectType, &resp.ValueFields, &resp.LabelFields) 591 rows.Scan(&resp.ObjectType, &resp.ValueFields, &resp.LabelFields)
581 } 592 }
582 if rows.Err() != nil { 593 if rows.Err() != nil {
583 return rows.Err() 594 return rows.Err()
584 } 595 }
585 596
586 list.LiveGraph = resp 597 list.LiveGraph = resp
587 598
588 return nil 599 return nil
589 } 600 }
590 601
1 package webutility 1 package webutility
2 2
3 import ( 3 import (
4 "encoding/json" 4 "encoding/json"
5 "fmt" 5 "fmt"
6 "io/ioutil" 6 "io/ioutil"
7 "net/http" 7 "net/http"
8 "path" 8 "path"
9 "strconv" 9 "strconv"
10 "strings" 10 "strings"
11 "sync" 11 "sync"
12 ) 12 )
13 13
14 // Dictionary ...
14 type Dictionary struct { 15 type Dictionary struct {
15 my sync.Mutex 16 my sync.Mutex
16 locales map[string]map[string]string 17 locales map[string]map[string]string
17 supported []string 18 supported []string
18 defaultLocale string 19 defaultLocale string
19 } 20 }
20 21
22 // NewDictionary ...
21 func NewDictionary() *Dictionary { 23 func NewDictionary() *Dictionary {
22 return &Dictionary{ 24 return &Dictionary{
23 locales: map[string]map[string]string{}, 25 locales: map[string]map[string]string{},
24 } 26 }
25 } 27 }
26 28
29 // AddTranslations ...
27 func (d *Dictionary) AddTranslations(directory string) error { 30 func (d *Dictionary) AddTranslations(directory string) error {
28 files, err := ioutil.ReadDir(directory) 31 files, err := ioutil.ReadDir(directory)
29 if err != nil { 32 if err != nil {
30 return err 33 return err
31 } 34 }
32 35
33 for _, fileInfo := range files { 36 for _, fileInfo := range files {
34 fName := fileInfo.Name() 37 fName := fileInfo.Name()
35 path := directory + "/" + fName 38 path := directory + "/" + fName
36 file, err := ioutil.ReadFile(path) 39 file, err := ioutil.ReadFile(path)
37 if err != nil { 40 if err != nil {
38 return err 41 return err
39 } 42 }
40 43
41 loc := stripFileExtension(fName) 44 loc := stripFileExtension(fName)
42 45
43 var data interface{} 46 var data interface{}
44 err = json.Unmarshal(file, &data) 47 err = json.Unmarshal(file, &data)
45 if err != nil { 48 if err != nil {
46 return err 49 return err
47 } 50 }
48 51
49 l := map[string]string{} 52 l := map[string]string{}
50 for k, v := range data.(map[string]interface{}) { 53 for k, v := range data.(map[string]interface{}) {
51 l[k] = v.(string) 54 l[k] = v.(string)
52 } 55 }
53 56
54 mu.Lock() 57 mu.Lock()
55 defer mu.Unlock() 58 defer mu.Unlock()
56 d.locales[loc] = l 59 d.locales[loc] = l
57 d.supported = append(d.supported, loc) 60 d.supported = append(d.supported, loc)
58 } 61 }
59 62
60 if d.defaultLocale == "" && len(d.supported) > 0 { 63 if d.defaultLocale == "" && len(d.supported) > 0 {
61 d.defaultLocale = d.supported[0] 64 d.defaultLocale = d.supported[0]
62 } 65 }
63 66
64 return nil 67 return nil
65 } 68 }
66 69
70 // GetBestMatchLocale ...
67 func (d *Dictionary) GetBestMatchLocale(req *http.Request) (best string) { 71 func (d *Dictionary) GetBestMatchLocale(req *http.Request) (best string) {
68 accepted := d.parseAcceptedLanguages(req.Header.Get("Accept-Language")) 72 accepted := d.parseAcceptedLanguages(req.Header.Get("Accept-Language"))
69 73
70 for i, _ := range accepted { 74 for i := range accepted {
71 if accepted[i].Code == "*" { 75 if accepted[i].Code == "*" {
72 return d.defaultLocale 76 return d.defaultLocale
73 } 77 }
74 for j, _ := range d.supported { 78 for j := range d.supported {
75 if accepted[i].Code == d.supported[j] { 79 if accepted[i].Code == d.supported[j] {
76 return d.supported[j] 80 return d.supported[j]
77 } 81 }
78 } 82 }
79 } 83 }
80 84
81 return d.defaultLocale 85 return d.defaultLocale
82 } 86 }
83 87
88 // Translate ...
84 func (d *Dictionary) Translate(loc, key string) string { 89 func (d *Dictionary) Translate(loc, key string) string {
85 return d.locales[loc][key] 90 return d.locales[loc][key]
86 } 91 }
87 92
93 // SetDefaultLocale ...
88 func (d *Dictionary) SetDefaultLocale(loc string) error { 94 func (d *Dictionary) SetDefaultLocale(loc string) error {
89 if !d.contains(loc) { 95 if !d.contains(loc) {
90 return fmt.Errorf("locale file not loaded: %s", loc) 96 return fmt.Errorf("locale file not loaded: %s", loc)
91 } 97 }
92 98
93 d.defaultLocale = loc 99 d.defaultLocale = loc
94 100
95 return nil 101 return nil
96 } 102 }
97 103
98 func (d *Dictionary) contains(loc string) bool { 104 func (d *Dictionary) contains(loc string) bool {
99 for _, v := range d.supported { 105 for _, v := range d.supported {
100 if v == loc { 106 if v == loc {
101 return true 107 return true
102 } 108 }
103 } 109 }
104 return false 110 return false
105 } 111 }
106 112
113 // LangWeight ...
107 type LangWeight struct { 114 type LangWeight struct {
108 Code string 115 Code string
109 Weight float64 116 Weight float64
110 } 117 }
111 118
112 func (d *Dictionary) parseAcceptedLanguages(accepted string) (langs []LangWeight) { 119 func (d *Dictionary) parseAcceptedLanguages(accepted string) (langs []LangWeight) {
113 if accepted == "" { 120 if accepted == "" {
114 langs = append(langs, LangWeight{Code: d.defaultLocale, Weight: 1.0}) 121 langs = append(langs, LangWeight{Code: d.defaultLocale, Weight: 1.0})
115 return langs 122 return langs
116 } 123 }
117 124
125 var code string
126 var weight float64
127
118 parts := strings.Split(accepted, ",") 128 parts := strings.Split(accepted, ",")
119 for i, _ := range parts { 129 for i := range parts {
120 parts[i] = strings.Trim(parts[i], " ") 130 parts[i] = strings.Trim(parts[i], " ")
121 131
122 var code string = ""
123 var weight float64 = 0.0
124
125 cw := strings.Split(parts[i], ";") 132 cw := strings.Split(parts[i], ";")
126 paramCount := len(cw) 133 paramCount := len(cw)
127 134
128 if paramCount == 1 { 135 if paramCount == 1 {
129 // default weight of 1 136 // default weight of 1
130 code = cw[0] 137 code = cw[0]
131 weight = 1.0 138 weight = 1.0
132 } else if paramCount == 2 { 139 } else if paramCount == 2 {
133 // parse weight 140 // parse weight
134 code = cw[0] 141 code = cw[0]
135 weight, _ = strconv.ParseFloat(cw[1][2:], 64) 142 weight, _ = strconv.ParseFloat(cw[1][2:], 64)
136 143
137 } 144 }
138 145
139 langs = append(langs, LangWeight{Code: code, Weight: weight}) 146 langs = append(langs, LangWeight{Code: code, Weight: weight})
140 } 147 }
141 148
142 // TODO(marko): sort langs by weights? 149 // TODO(marko): sort langs by weights?
143 150
144 return langs 151 return langs
145 } 152 }
146 153
147 func stripFileExtension(full string) (stripped string) { 154 func stripFileExtension(full string) (stripped string) {
148 extension := path.Ext(full) 155 extension := path.Ext(full)
149 stripped = strings.TrimSuffix(full, extension) 156 stripped = strings.TrimSuffix(full, extension)
1 package webutility 1 package webutility
2 2
3 import ( 3 import (
4 "net/http" 4 "net/http"
5 "time" 5 "time"
6 6
7 "git.to-net.rs/marko.tikvic/gologger" 7 "git.to-net.rs/marko.tikvic/gologger"
8 ) 8 )
9 9
10 var httpLogger *gologger.Logger 10 var httpLogger *gologger.Logger
11 11
12 // SetHeaders ...
12 func SetHeaders(h http.HandlerFunc) http.HandlerFunc { 13 func SetHeaders(h http.HandlerFunc) http.HandlerFunc {
13 return func(w http.ResponseWriter, req *http.Request) { 14 return func(w http.ResponseWriter, req *http.Request) {
14 SetDefaultHeaders(w) 15 SetDefaultHeaders(w)
15 if req.Method == http.MethodOptions { 16 if req.Method == http.MethodOptions {
16 return 17 return
17 } 18 }
18 h(w, req) 19 h(w, req)
19 } 20 }
20 } 21 }
21 22
23 // ParseForm ...
22 func ParseForm(h http.HandlerFunc) http.HandlerFunc { 24 func ParseForm(h http.HandlerFunc) http.HandlerFunc {
23 return func(w http.ResponseWriter, req *http.Request) { 25 return func(w http.ResponseWriter, req *http.Request) {
24 err := req.ParseForm() 26 err := req.ParseForm()
25 if err != nil { 27 if err != nil {
26 BadRequest(w, req, err.Error()) 28 BadRequest(w, req, err.Error())
27 return 29 return
28 } 30 }
29 h(w, req) 31 h(w, req)
30 } 32 }
31 } 33 }
32 34
35 // ParseMultipartForm ...
33 func ParseMultipartForm(h http.HandlerFunc) http.HandlerFunc { 36 func ParseMultipartForm(h http.HandlerFunc) http.HandlerFunc {
34 return func(w http.ResponseWriter, req *http.Request) { 37 return func(w http.ResponseWriter, req *http.Request) {
35 err := req.ParseMultipartForm(32 << 20) 38 err := req.ParseMultipartForm(32 << 20)
36 if err != nil { 39 if err != nil {
37 BadRequest(w, req, err.Error()) 40 BadRequest(w, req, err.Error())
38 return 41 return
39 } 42 }
40 h(w, req) 43 h(w, req)
41 } 44 }
42 } 45 }
43 46
47 // EnableLogging ...
44 func EnableLogging(log string) (err error) { 48 func EnableLogging(log string) (err error) {
45 httpLogger, err = gologger.New(log, gologger.MaxLogSize5MB) 49 httpLogger, err = gologger.New(log, gologger.MaxLogSize5MB)
46 return err 50 return err
47 } 51 }
48 52
53 // Log ...
49 func Log(h http.HandlerFunc) http.HandlerFunc { 54 func Log(h http.HandlerFunc) http.HandlerFunc {
50 return func(w http.ResponseWriter, req *http.Request) { 55 return func(w http.ResponseWriter, req *http.Request) {
51 t1 := time.Now() 56 t1 := time.Now()
52 57
53 claims, _ := GetTokenClaims(req) 58 claims, _ := GetTokenClaims(req)
54 in := httpLogger.LogHTTPRequest(req, claims.Username) 59 in := httpLogger.LogHTTPRequest(req, claims.Username)
55 60
56 rec := NewStatusRecorder(w) 61 rec := NewStatusRecorder(w)
57 62
58 h(rec, req) 63 h(rec, req)
59 64
60 t2 := time.Now() 65 t2 := time.Now()
61 out := httpLogger.LogHTTPResponse(rec.Status(), t2.Sub(t1), rec.Size()) 66 out := httpLogger.LogHTTPResponse(rec.Status(), t2.Sub(t1), rec.Size())
62 67
63 httpLogger.CombineHTTPLogs(in, out) 68 httpLogger.CombineHTTPLogs(in, out)
64 } 69 }
65 } 70 }
66 71
72 // Auth ...
67 func Auth(roles string, h http.HandlerFunc) http.HandlerFunc { 73 func Auth(roles string, h http.HandlerFunc) http.HandlerFunc {
68 return func(w http.ResponseWriter, req *http.Request) { 74 return func(w http.ResponseWriter, req *http.Request) {
69 if _, err := AuthCheck(req, roles); err != nil { 75 if _, err := AuthCheck(req, roles); err != nil {
70 Unauthorized(w, req, err.Error()) 76 Unauthorized(w, req, err.Error())
71 return 77 return
72 } 78 }
73 h(w, req) 79 h(w, req)
74 } 80 }
75 } 81 }
82
76 83
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 "io" 8 "io"
9 "net/http" 9 "net/http"
10 "os" 10 "os"
11 "strings" 11 "strings"
12 "sync" 12 "sync"
13 "time" 13 "time"
14 14
15 "git.to-net.rs/marko.tikvic/gologger" 15 "git.to-net.rs/marko.tikvic/gologger"
16 ) 16 )
17 17
18 var ( 18 var (
19 mu = &sync.Mutex{} 19 mu = &sync.Mutex{}
20 metadata = make(map[string]Payload) 20 metadata = make(map[string]Payload)
21 21
22 updateQue = make(map[string][]byte) 22 updateQue = make(map[string][]byte)
23 23
24 metadataDB *sql.DB 24 metadataDB *sql.DB
25 activeProject string 25 activeProject string
26 26
27 inited bool 27 inited bool
28 driver string 28 driver string
29 logger *gologger.Logger 29 logger *gologger.Logger
30 ) 30 )
31 31
32 // LangMap ...
32 type LangMap map[string]map[string]string 33 type LangMap map[string]map[string]string
33 34
35 // Field ...
34 type Field struct { 36 type Field struct {
35 Parameter string `json:"param"` 37 Parameter string `json:"param"`
36 Type string `json:"type"` 38 Type string `json:"type"`
37 Visible bool `json:"visible"` 39 Visible bool `json:"visible"`
38 Editable bool `json:"editable"` 40 Editable bool `json:"editable"`
39 } 41 }
40 42
43 // CorrelationField ...
41 type CorrelationField struct { 44 type CorrelationField struct {
42 Result string `json:"result"` 45 Result string `json:"result"`
43 Elements []string `json:"elements"` 46 Elements []string `json:"elements"`
44 Type string `json:"type"` 47 Type string `json:"type"`
45 } 48 }
46 49
50 // Translation ...
47 type Translation struct { 51 type Translation struct {
48 Language string `json:"language"` 52 Language string `json:"language"`
49 FieldsLabels map[string]string `json:"fieldsLabels"` 53 FieldsLabels map[string]string `json:"fieldsLabels"`
50 } 54 }
51 55
52 // output 56 // PaginationLinks ...
53 type PaginationLinks struct { 57 type PaginationLinks struct {
54 Base string `json:"base"` 58 Base string `json:"base"`
55 Next string `json:"next"` 59 Next string `json:"next"`
56 Prev string `json:"prev"` 60 Prev string `json:"prev"`
57 Self string `json:"self"` 61 Self string `json:"self"`
58 } 62 }
59 63
60 // input 64 // PaginationParameters ...
61 type PaginationParameters struct { 65 type PaginationParameters struct {
62 URL string `json:"-"` 66 URL string `json:"-"`
63 Offset int64 `json:"offset"` 67 Offset int64 `json:"offset"`
64 Limit int64 `json:"limit"` 68 Limit int64 `json:"limit"`
65 SortBy string `json:"sortBy"` 69 SortBy string `json:"sortBy"`
66 Order string `json:"order"` 70 Order string `json:"order"`
67 } 71 }
68 72
73 // GetPaginationParameters ...
69 // TODO(marko) 74 // TODO(marko)
70 func GetPaginationParameters(req *http.Request) (p PaginationParameters) { 75 func GetPaginationParameters(req *http.Request) (p PaginationParameters) {
71 return p 76 return p
72 } 77 }
73 78
74 // TODO(marko) 79 // TODO(marko)
75 func (p *PaginationParameters) paginationLinks() (links PaginationLinks) { 80 func (p *PaginationParameters) paginationLinks() (links PaginationLinks) {
76 return links 81 return links
77 } 82 }
78 83
84 // Payload ...
79 type Payload struct { 85 type Payload struct {
80 Method string `json:"method"` 86 Method string `json:"method"`
81 Params map[string]string `json:"params"` 87 Params map[string]string `json:"params"`
82 Lang []Translation `json:"lang"` 88 Lang []Translation `json:"lang"`
83 Fields []Field `json:"fields"` 89 Fields []Field `json:"fields"`
84 Correlations []CorrelationField `json:"correlationFields"` 90 Correlations []CorrelationField `json:"correlationFields"`
85 IdField string `json:"idField"` 91 IDField string `json:"idField"`
86 92
87 // Pagination 93 // Pagination
88 Count int64 `json:"count"` 94 Count int64 `json:"count"`
89 Total int64 `json:"total"` 95 Total int64 `json:"total"`
90 Links PaginationLinks `json:"_links"` 96 Links PaginationLinks `json:"_links"`
91 97
92 // Data holds JSON payload. It can't be used for itteration. 98 // Data holds JSON payload. It can't be used for itteration.
93 Data interface{} `json:"data"` 99 Data interface{} `json:"data"`
94 } 100 }
95 101
96 func (p *Payload) addLang(code string, labels map[string]string) { 102 func (p *Payload) addLang(code string, labels map[string]string) {
97 t := Translation{ 103 t := Translation{
98 Language: code, 104 Language: code,
99 FieldsLabels: labels, 105 FieldsLabels: labels,
100 } 106 }
101 p.Lang = append(p.Lang, t) 107 p.Lang = append(p.Lang, t)
102 } 108 }
103 109
110 // SetData ...
104 func (p *Payload) SetData(data interface{}) { 111 func (p *Payload) SetData(data interface{}) {
105 p.Data = data 112 p.Data = data
106 } 113 }
107 114
115 // SetPaginationInfo ...
108 func (p *Payload) SetPaginationInfo(count, total int64, params PaginationParameters) { 116 func (p *Payload) SetPaginationInfo(count, total int64, params PaginationParameters) {
109 p.Count = count 117 p.Count = count
110 p.Total = total 118 p.Total = total
111 p.Links = params.paginationLinks() 119 p.Links = params.paginationLinks()
112 } 120 }
113 121
114 // NewPayload returs a payload sceleton for entity described with key. 122 // NewPayload returs a payload sceleton for entity described with key.
115 func NewPayload(r *http.Request, key string) Payload { 123 func NewPayload(r *http.Request, key string) Payload {
116 p := metadata[key] 124 p := metadata[key]
117 p.Method = r.Method + " " + r.RequestURI 125 p.Method = r.Method + " " + r.RequestURI
118 return p 126 return p
119 } 127 }
120 128
121 // DecodeJSON decodes JSON data from r to v. 129 // DecodeJSON decodes JSON data from r to v.
122 // Returns an error if it fails. 130 // Returns an error if it fails.
123 func DecodeJSON(r io.Reader, v interface{}) error { 131 func DecodeJSON(r io.Reader, v interface{}) error {
124 return json.NewDecoder(r).Decode(v) 132 return json.NewDecoder(r).Decode(v)
125 } 133 }
126 134
127 // InitPayloadsMetadata loads all payloads' information into 'metadata' variable. 135 // InitPayloadsMetadata loads all payloads' information into 'metadata' variable.
128 func InitPayloadsMetadata(drv string, db *sql.DB, project string) error { 136 func InitPayloadsMetadata(drv string, db *sql.DB, project string) error {
129 var err error 137 var err error
130 if drv != "ora" && drv != "mysql" { 138 if drv != "ora" && drv != "mysql" {
131 err = errors.New("driver not supported") 139 err = errors.New("driver not supported")
132 return err 140 return err
133 } 141 }
134 142
135 driver = drv 143 driver = drv
136 metadataDB = db 144 metadataDB = db
137 activeProject = project 145 activeProject = project
138 146
139 logger, err = gologger.New("metadata", gologger.MaxLogSize100KB) 147 logger, err = gologger.New("metadata", gologger.MaxLogSize100KB)
140 if err != nil { 148 if err != nil {
141 fmt.Printf("webutility: %s\n", err.Error()) 149 fmt.Printf("webutility: %s\n", err.Error())
142 } 150 }
143 151
144 mu.Lock() 152 mu.Lock()
145 defer mu.Unlock() 153 defer mu.Unlock()
146 err = initMetadata(project) 154 err = initMetadata(project)
147 if err != nil { 155 if err != nil {
148 return err 156 return err
149 } 157 }
150 inited = true 158 inited = true
151 159
152 return nil 160 return nil
153 } 161 }
154 162
163 // EnableHotloading ...
155 func EnableHotloading(interval int) { 164 func EnableHotloading(interval int) {
156 if interval > 0 { 165 if interval > 0 {
157 go hotload(interval) 166 go hotload(interval)
158 } 167 }
159 } 168 }
160 169
170 // GetMetadataForAllEntities ...
161 func GetMetadataForAllEntities() map[string]Payload { 171 func GetMetadataForAllEntities() map[string]Payload {
162 return metadata 172 return metadata
163 } 173 }
164 174
175 // GetMetadataForEntity ...
165 func GetMetadataForEntity(t string) (Payload, bool) { 176 func GetMetadataForEntity(t string) (Payload, bool) {
166 p, ok := metadata[t] 177 p, ok := metadata[t]
167 return p, ok 178 return p, ok
168 } 179 }
169 180
181 // QueEntityModelUpdate ...
170 func QueEntityModelUpdate(entityType string, v interface{}) { 182 func QueEntityModelUpdate(entityType string, v interface{}) {
171 updateQue[entityType], _ = json.Marshal(v) 183 updateQue[entityType], _ = json.Marshal(v)
172 } 184 }
173 185
186 // UpdateEntityModels ...
174 func UpdateEntityModels(command string) (total, upd, add int, err error) { 187 func UpdateEntityModels(command string) (total, upd, add int, err error) {
175 if command != "force" && command != "missing" { 188 if command != "force" && command != "missing" {
176 return total, 0, 0, errors.New("webutility: unknown command: " + command) 189 return total, 0, 0, errors.New("webutility: unknown command: " + command)
177 } 190 }
178 191
179 if !inited { 192 if !inited {
180 return 0, 0, 0, errors.New("webutility: metadata not initialized but update was tried.") 193 return 0, 0, 0, errors.New("webutility: metadata not initialized but update was tried")
181 } 194 }
182 195
183 total = len(updateQue) 196 total = len(updateQue)
184 197
185 toUpdate := make([]string, 0) 198 toUpdate := make([]string, 0)
186 toAdd := make([]string, 0) 199 toAdd := make([]string, 0)
187 200
188 for k, _ := range updateQue { 201 for k := range updateQue {
189 if _, exists := metadata[k]; exists { 202 if _, exists := metadata[k]; exists {
190 if command == "force" { 203 if command == "force" {
191 toUpdate = append(toUpdate, k) 204 toUpdate = append(toUpdate, k)
192 } 205 }
193 } else { 206 } else {
194 toAdd = append(toAdd, k) 207 toAdd = append(toAdd, k)
195 } 208 }
196 } 209 }
197 210
198 var uStmt *sql.Stmt 211 var uStmt *sql.Stmt
199 if driver == "ora" { 212 if driver == "ora" {
200 uStmt, err = metadataDB.Prepare("update entities set entity_model = :1 where entity_type = :2") 213 uStmt, err = metadataDB.Prepare("update entities set entity_model = :1 where entity_type = :2")
201 if err != nil { 214 if err != nil {
202 logger.Trace(err.Error()) 215 logger.Trace(err.Error())
203 return 216 return
204 } 217 }
205 } else if driver == "mysql" { 218 } else if driver == "mysql" {
206 uStmt, err = metadataDB.Prepare("update entities set entity_model = ? where entity_type = ?") 219 uStmt, err = metadataDB.Prepare("update entities set entity_model = ? where entity_type = ?")
207 if err != nil { 220 if err != nil {
208 logger.Trace(err.Error()) 221 logger.Trace(err.Error())
209 return 222 return
210 } 223 }
211 } 224 }
212 for _, k := range toUpdate { 225 for _, k := range toUpdate {
213 _, err = uStmt.Exec(string(updateQue[k]), k) 226 _, err = uStmt.Exec(string(updateQue[k]), k)
214 if err != nil { 227 if err != nil {
215 logger.Trace(err.Error()) 228 logger.Trace(err.Error())
216 return 229 return
217 } 230 }
218 upd++ 231 upd++
219 } 232 }
220 233
221 blankPayload, _ := json.Marshal(Payload{}) 234 blankPayload, _ := json.Marshal(Payload{})
222 var iStmt *sql.Stmt 235 var iStmt *sql.Stmt
223 if driver == "ora" { 236 if driver == "ora" {
224 iStmt, err = metadataDB.Prepare("insert into entities(projekat, metadata, entity_type, entity_model) values(:1, :2, :3, :4)") 237 iStmt, err = metadataDB.Prepare("insert into entities(projekat, metadata, entity_type, entity_model) values(:1, :2, :3, :4)")
225 if err != nil { 238 if err != nil {
226 logger.Trace(err.Error()) 239 logger.Trace(err.Error())
227 return 240 return
228 } 241 }
229 } else if driver == "mysql" { 242 } else if driver == "mysql" {
230 iStmt, err = metadataDB.Prepare("insert into entities(projekat, metadata, entity_type, entity_model) values(?, ?, ?, ?)") 243 iStmt, err = metadataDB.Prepare("insert into entities(projekat, metadata, entity_type, entity_model) values(?, ?, ?, ?)")
231 if err != nil { 244 if err != nil {
232 logger.Trace(err.Error()) 245 logger.Trace(err.Error())
233 return 246 return
234 } 247 }
235 } 248 }
236 for _, k := range toAdd { 249 for _, k := range toAdd {
237 _, err = iStmt.Exec(activeProject, string(blankPayload), k, string(updateQue[k])) 250 _, err = iStmt.Exec(activeProject, string(blankPayload), k, string(updateQue[k]))
238 if err != nil { 251 if err != nil {
239 logger.Trace(err.Error()) 252 logger.Trace(err.Error())
240 return 253 return
241 } 254 }
242 metadata[k] = Payload{} 255 metadata[k] = Payload{}
243 add++ 256 add++
244 } 257 }
245 258
246 return total, upd, add, nil 259 return total, upd, add, nil
247 } 260 }
248 261
249 func initMetadata(project string) error { 262 func initMetadata(project string) error {
250 rows, err := metadataDB.Query(`select 263 rows, err := metadataDB.Query(`select
251 entity_type, 264 entity_type,
252 metadata 265 metadata
253 from entities 266 from entities
254 where projekat = ` + fmt.Sprintf("'%s'", project)) 267 where projekat = ` + fmt.Sprintf("'%s'", project))
255 if err != nil { 268 if err != nil {
256 return err 269 return err
257 } 270 }
258 defer rows.Close() 271 defer rows.Close()
259 272
260 if len(metadata) > 0 { 273 if len(metadata) > 0 {
261 metadata = nil 274 metadata = nil
262 } 275 }
263 metadata = make(map[string]Payload) 276 metadata = make(map[string]Payload)
264 for rows.Next() { 277 for rows.Next() {
265 var name, load string 278 var name, load string
266 rows.Scan(&name, &load) 279 rows.Scan(&name, &load)
267 280
268 p := Payload{} 281 p := Payload{}
269 err := json.Unmarshal([]byte(load), &p) 282 err := json.Unmarshal([]byte(load), &p)
270 if err != nil { 283 if err != nil {
271 logger.Log("webutility: couldn't init: '%s' metadata: %s:\n%s\n", name, err.Error(), load) 284 logger.Log("webutility: couldn't init: '%s' metadata: %s:\n%s\n", name, err.Error(), load)
272 } else { 285 } else {
273 metadata[name] = p 286 metadata[name] = p
274 } 287 }
275 } 288 }
276 289
277 return nil 290 return nil
278 } 291 }
279 292
280 // TODO(marko): 293 // LoadMetadataFromFile expects file in format:
281 //
282 // Currently supports only one hardcoded language...
283 //
284 //
285 //
286 //
287 //
288 // Metadata file ecpected format:
289 // 294 //
290 // [ payload A identifier ] 295 // [ payload A identifier ]
291 // key1 : value1 296 // key1 : value1
292 // key2 : value2 297 // key2 : value2
293 // ... 298 // ...
294 // [ payload B identifier ] 299 // [ payload B identifier ]
295 // key1 : value1 300 // key1 : value1
296 // key2 : value2 301 // key2 : value2
297 // ... 302 // ...
303 //
304 // TODO(marko): Currently supports only one hardcoded language...
298 func LoadMetadataFromFile(path string) error { 305 func LoadMetadataFromFile(path string) error {
299 lines, err := ReadFileLines(path) 306 lines, err := ReadFileLines(path)
300 if err != nil { 307 if err != nil {
301 return err 308 return err
302 } 309 }
303 310
304 metadata = make(map[string]Payload) 311 metadata = make(map[string]Payload)
305 312
306 var name string 313 var name string
307 for i, l := range lines { 314 for i, l := range lines {
308 // skip empty lines 315 // skip empty lines
309 if l = trimSpaces(l); len(l) == 0 { 316 if l = trimSpaces(l); len(l) == 0 {
310 continue 317 continue
311 } 318 }
312 319
313 if isWrappedWith(l, "[", "]") { 320 if isWrappedWith(l, "[", "]") {
314 name = strings.Trim(l, "[]") 321 name = strings.Trim(l, "[]")
315 p := Payload{} 322 p := Payload{}
316 p.addLang("sr", make(map[string]string)) 323 p.addLang("sr", make(map[string]string))
317 metadata[name] = p 324 metadata[name] = p
318 continue 325 continue
319 } 326 }
320 327
321 if name == "" { 328 if name == "" {
322 return fmt.Errorf("webutility: LoadMetadataFromFile: error on line %d: [no header] [%s]\n", i+1, l) 329 return fmt.Errorf("webutility: LoadMetadataFromFile: error on line %d: [no header] [%s]", i+1, l)
323 } 330 }
324 331
325 parts := strings.Split(l, ":") 332 parts := strings.Split(l, ":")
326 if len(parts) != 2 { 333 if len(parts) != 2 {
327 return fmt.Errorf("webutility: LoadMetadataFromFile: error on line %d: [invalid format] [%s]\n", i+1, l) 334 return fmt.Errorf("webutility: LoadMetadataFromFile: error on line %d: [invalid format] [%s]", i+1, l)
328 } 335 }
329 336
330 k := trimSpaces(parts[0]) 337 k := trimSpaces(parts[0])
331 v := trimSpaces(parts[1]) 338 v := trimSpaces(parts[1])
332 if v != "-" { 339 if v != "-" {
333 metadata[name].Lang[0].FieldsLabels[k] = v 340 metadata[name].Lang[0].FieldsLabels[k] = v
334 } 341 }
335 } 342 }
336 343
337 return nil 344 return nil
338 } 345 }
339 346
340 func isWrappedWith(src, begin, end string) bool { 347 func isWrappedWith(src, begin, end string) bool {
341 return strings.HasPrefix(src, begin) && strings.HasSuffix(src, end) 348 return strings.HasPrefix(src, begin) && strings.HasSuffix(src, end)
342 } 349 }
343 350
344 func trimSpaces(s string) string { 351 func trimSpaces(s string) string {
345 return strings.TrimSpace(s) 352 return strings.TrimSpace(s)
346 } 353 }
347 354
355 // ReadFileLines ...
348 // TODO(marko): Move to separate package 356 // TODO(marko): Move to separate package
349 func ReadFileLines(path string) ([]string, error) { 357 func ReadFileLines(path string) ([]string, error) {
350 f, err := os.Open(path) 358 f, err := os.Open(path)
351 if err != nil { 359 if err != nil {
352 return nil, err 360 return nil, err
353 } 361 }
354 defer f.Close() 362 defer f.Close()
355 363
356 var s strings.Builder 364 var s strings.Builder
357 365
358 if _, err = io.Copy(&s, f); err != nil { 366 if _, err = io.Copy(&s, f); err != nil {
359 return nil, err 367 return nil, err
360 } 368 }
361 369
362 lines := strings.Split(s.String(), "\n") 370 lines := strings.Split(s.String(), "\n")
363 371
364 return lines, nil 372 return lines, nil
365 } 373 }
366 374
367 func hotload(n int) { 375 func hotload(n int) {
368 entityScan := make(map[string]int64) 376 entityScan := make(map[string]int64)
369 firstCheck := true 377 firstCheck := true
370 for { 378 for {
371 time.Sleep(time.Duration(n) * time.Second) 379 time.Sleep(time.Duration(n) * time.Second)
372 rows, err := metadataDB.Query(`select 380 rows, err := metadataDB.Query(`select
373 ora_rowscn, 381 ora_rowscn,
374 entity_type 382 entity_type
375 from entities where projekat = ` + fmt.Sprintf("'%s'", activeProject)) 383 from entities where projekat = ` + fmt.Sprintf("'%s'", activeProject))
376 if err != nil { 384 if err != nil {
377 logger.Log("webutility: hotload failed: %v\n", err) 385 logger.Log("webutility: hotload failed: %v\n", err)
378 time.Sleep(time.Duration(n) * time.Second) 386 time.Sleep(time.Duration(n) * time.Second)
379 continue 387 continue
380 } 388 }
381 389
382 var toRefresh []string 390 var toRefresh []string
383 for rows.Next() { 391 for rows.Next() {
384 var scanID int64 392 var scanID int64
385 var entity string 393 var entity string
386 rows.Scan(&scanID, &entity) 394 rows.Scan(&scanID, &entity)
387 oldID, ok := entityScan[entity] 395 oldID, ok := entityScan[entity]
388 if !ok || oldID != scanID { 396 if !ok || oldID != scanID {
389 entityScan[entity] = scanID 397 entityScan[entity] = scanID
390 toRefresh = append(toRefresh, entity) 398 toRefresh = append(toRefresh, entity)
391 } 399 }
392 } 400 }
393 rows.Close() 401 rows.Close()
394 402
395 if rows.Err() != nil { 403 if rows.Err() != nil {
396 logger.Log("webutility: hotload rset error: %v\n", rows.Err()) 404 logger.Log("webutility: hotload rset error: %v\n", rows.Err())
397 time.Sleep(time.Duration(n) * time.Second) 405 time.Sleep(time.Duration(n) * time.Second)
398 continue 406 continue
399 } 407 }
400 408
401 if len(toRefresh) > 0 && !firstCheck { 409 if len(toRefresh) > 0 && !firstCheck {
402 mu.Lock() 410 mu.Lock()
403 refreshMetadata(toRefresh) 411 refreshMetadata(toRefresh)
404 mu.Unlock() 412 mu.Unlock()
405 } 413 }
406 if firstCheck { 414 if firstCheck {
407 firstCheck = false 415 firstCheck = false
408 } 416 }
409 } 417 }
410 } 418 }
411 419
412 func refreshMetadata(entities []string) { 420 func refreshMetadata(entities []string) {
413 for _, e := range entities { 421 for _, e := range entities {
414 fmt.Printf("refreshing %s\n", e) 422 fmt.Printf("refreshing %s\n", e)
415 rows, err := metadataDB.Query(`select 423 rows, err := metadataDB.Query(`select
416 metadata 424 metadata
417 from entities 425 from entities
418 where projekat = ` + fmt.Sprintf("'%s'", activeProject) + 426 where projekat = ` + fmt.Sprintf("'%s'", activeProject) +
419 ` and entity_type = ` + fmt.Sprintf("'%s'", e)) 427 ` and entity_type = ` + fmt.Sprintf("'%s'", e))
420 428
421 if err != nil { 429 if err != nil {
422 logger.Log("webutility: refresh: prep: %v\n", err) 430 logger.Log("webutility: refresh: prep: %v\n", err)
423 rows.Close() 431 rows.Close()
424 continue 432 continue
425 } 433 }
426 434
427 for rows.Next() { 435 for rows.Next() {
428 var load string 436 var load string
429 rows.Scan(&load) 437 rows.Scan(&load)
430 p := Payload{} 438 p := Payload{}
431 err := json.Unmarshal([]byte(load), &p) 439 err := json.Unmarshal([]byte(load), &p)
432 if err != nil { 440 if err != nil {
433 logger.Log("webutility: couldn't refresh: '%s' metadata: %s\n%s\n", e, err.Error(), load) 441 logger.Log("webutility: couldn't refresh: '%s' metadata: %s\n%s\n", e, err.Error(), load)
434 } else { 442 } else {
435 metadata[e] = p 443 metadata[e] = p
436 } 444 }
437 } 445 }
438 rows.Close() 446 rows.Close()
439 } 447 }
440 } 448 }
441 449
442 /* 450 /*
443 func ModifyMetadataForEntity(entityType string, p *Payload) error { 451 func ModifyMetadataForEntity(entityType string, p *Payload) error {
444 md, err := json.Marshal(*p) 452 md, err := json.Marshal(*p)
445 if err != nil { 453 if err != nil {
446 return err 454 return err
447 } 455 }
448 456
449 mu.Lock() 457 mu.Lock()
450 defer mu.Unlock() 458 defer mu.Unlock()
451 _, err = metadataDB.PrepAndExe(`update entities set 459 _, err = metadataDB.PrepAndExe(`update entities set
452 metadata = :1 460 metadata = :1
453 where projekat = :2 461 where projekat = :2
454 and entity_type = :3`, 462 and entity_type = :3`,
455 string(md), 463 string(md),
456 activeProject, 464 activeProject,
457 entityType) 465 entityType)
458 if err != nil { 466 if err != nil {
459 return err 467 return err
460 } 468 }
461 return nil 469 return nil
462 } 470 }
463 471
464 func DeleteEntityModel(entityType string) error { 472 func DeleteEntityModel(entityType string) error {
465 _, err := metadataDB.PrepAndExe("delete from entities where entity_type = :1", entityType) 473 _, err := metadataDB.PrepAndExe("delete from entities where entity_type = :1", entityType)
466 if err == nil { 474 if err == nil {
1 package webutility 1 package webutility
2 2
3 import "database/sql" 3 import "database/sql"
4 4
5 // SelectConfig ...
5 type SelectConfig struct { 6 type SelectConfig struct {
6 ListObjType string `json:"listObjectType"` 7 ListObjType string `json:"listObjectType"`
7 ObjType string `json:"objectType"` 8 ObjType string `json:"objectType"`
8 Type string `json:"type"` 9 Type string `json:"type"`
9 IdField string `json:"idField"` 10 IDField string `json:"idField"`
10 LabelField string `json:"labelField"` 11 LabelField string `json:"labelField"`
11 ValueField string `json:"valueField"` 12 ValueField string `json:"valueField"`
12 } 13 }
13 14
14 // GetSelectConfig returns select configuration slice for the given object type. 15 // GetSelectConfig returns select configuration slice for the given object type.
15 func GetSelectConfig(db *sql.DB, otype string) ([]SelectConfig, error) { 16 func GetSelectConfig(db *sql.DB, otype string) ([]SelectConfig, error) {
16 resp := make([]SelectConfig, 0) 17 resp := make([]SelectConfig, 0)
17 rows, err := db.Query(`SELECT 18 rows, err := db.Query(`SELECT
18 a.LIST_OBJECT_TYPE, 19 a.LIST_OBJECT_TYPE,
19 a.OBJECT_TYPE, 20 a.OBJECT_TYPE,
20 a.ID_FIELD, 21 a.ID_FIELD,
21 a.LABEL_FIELD, 22 a.LABEL_FIELD,
22 a.TYPE, 23 a.TYPE,
23 b.FIELD 24 b.FIELD
24 FROM LIST_SELECT_CONFIG a, LIST_VALUE_FIELD b 25 FROM LIST_SELECT_CONFIG a, LIST_VALUE_FIELD b
25 WHERE a.LIST_OBJECT_TYPE` + otype + ` 26 WHERE a.LIST_OBJECT_TYPE` + otype + `
26 AND b.LIST_TYPE = a.LIST_OBJECT_TYPE 27 AND b.LIST_TYPE = a.LIST_OBJECT_TYPE
27 AND b.OBJECT_TYPE = a.OBJECT_TYPE`) 28 AND b.OBJECT_TYPE = a.OBJECT_TYPE`)
28 if err != nil { 29 if err != nil {
29 return nil, err 30 return nil, err
30 } 31 }
31 defer rows.Close() 32 defer rows.Close()
32 33
33 var sc SelectConfig 34 var sc SelectConfig
34 for rows.Next() { 35 for rows.Next() {
35 rows.Scan(&sc.ListObjType, &sc.ObjType, &sc.IdField, &sc.LabelField, &sc.Type, &sc.ValueField) 36 rows.Scan(&sc.ListObjType, &sc.ObjType, &sc.IDField, &sc.LabelField, &sc.Type, &sc.ValueField)
36 resp = append(resp, sc) 37 resp = append(resp, sc)
37 } 38 }
38 if rows.Err() != nil { 39 if rows.Err() != nil {
39 return nil, rows.Err() 40 return nil, rows.Err()
40 } 41 }
41 42
42 return resp, nil 43 return resp, nil
43 } 44 }
44 45
string_sanitisation.go
1 package webutility 1 package webutility
2 2
3 import "strings" 3 import "strings"
4 4
5 var patern string = "\"';&*<>=\\`:" 5 const patern = "\"';&*<>=\\`:"
6 6
7 // SQLSafeString removes characters from s found in patern and returns new modified string. 7 // SanitiseString removes characters from s found in patern and returns new modified string.
8 func SanitiseString(s string) (safe string) { 8 func SanitiseString(s string) (safe string) {
9 for _, c := range patern { 9 for _, c := range patern {
10 safe = strings.Replace(s, string(c), "", -1) 10 safe = strings.Replace(s, string(c), "", -1)
11 } 11 }
12 return safe 12 return safe
13 } 13 }
14 14