Commit 707782344fd94987d3cd7c8426d674dd6d7b16a1
1 parent
954ce8ddd9
Exists in
master
lint; vet
Showing
12 changed files
with
186 additions
and
120 deletions
Show diff stats
auth.go
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 |
email.go
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 |
filtering.go
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 |
format.go
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 |
http.go
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 |
list_config.go
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 |
localization.go
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) |
middleware.go
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 |
payload.go
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 { |
select_config.go
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 |