Commit a205e8f40ddf1621558e6c29c3fed7aa2134f1a6
1 parent
f84e7607d3
Exists in
master
and in
1 other branch
changes
Showing
5 changed files
with
40 additions
and
13 deletions
Show diff stats
auth_utility.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 | const OneDay = time.Hour * 24 | 15 | const OneDay = time.Hour * 24 |
16 | const OneWeek = OneDay * 7 | 16 | const OneWeek = OneDay * 7 |
17 | const saltSize = 32 | 17 | const saltSize = 32 |
18 | 18 | ||
19 | var appName = "webutility" | 19 | var appName = "webutility" |
20 | var secret = "webutility" | 20 | var secret = "webutility" |
21 | 21 | ||
22 | type Role struct { | 22 | type Role struct { |
23 | Name string `json:"name"` | 23 | Name string `json:"name"` |
24 | ID int32 `json:"id"` | 24 | ID int64 `json:"id"` |
25 | } | 25 | } |
26 | 26 | ||
27 | // TokenClaims are JWT token claims. | 27 | // TokenClaims are JWT token claims. |
28 | type TokenClaims struct { | 28 | type TokenClaims struct { |
29 | // extending a struct | ||
30 | jwt.StandardClaims | ||
31 | |||
32 | // custom claims | ||
29 | Token string `json:"access_token"` | 33 | Token string `json:"access_token"` |
30 | TokenType string `json:"token_type"` | 34 | TokenType string `json:"token_type"` |
31 | Username string `json:"username"` | 35 | Username string `json:"username"` |
32 | Role string `json:"role"` | 36 | Role string `json:"role"` |
33 | RoleID int32 `json:"role_id"` | 37 | RoleID int64 `json:"role_id"` |
34 | ExpiresIn int64 `json:"expires_in"` | 38 | ExpiresIn int64 `json:"expires_in"` |
35 | |||
36 | // extending a struct | ||
37 | jwt.StandardClaims | ||
38 | } | 39 | } |
39 | 40 | ||
40 | func InitJWT(appName, secret string) { | 41 | func InitJWT(appName, secret string) { |
41 | appName = appName | 42 | appName = appName |
42 | secret = secret | 43 | secret = secret |
43 | } | 44 | } |
44 | 45 | ||
45 | // ValidateCredentials hashes pass and salt and returns comparison result with resultHash | 46 | // ValidateCredentials hashes pass and salt and returns comparison result with resultHash |
46 | func ValidateCredentials(pass, salt, resultHash string) (bool, error) { | 47 | func ValidateCredentials(pass, salt, resultHash string) (bool, error) { |
47 | hash, _, err := CreateHash(pass, salt) | 48 | hash, _, err := CreateHash(pass, salt) |
48 | if err != nil { | 49 | if err != nil { |
49 | return false, err | 50 | return false, err |
50 | } | 51 | } |
51 | res := hash == resultHash | 52 | res := hash == resultHash |
52 | return res, nil | 53 | return res, nil |
53 | } | 54 | } |
54 | 55 | ||
55 | // CreateHash hashes str using SHA256. | 56 | // CreateHash hashes str using SHA256. |
56 | // If the presalt parameter is not provided CreateHash will generate new salt string. | 57 | // If the presalt parameter is not provided CreateHash will generate new salt string. |
57 | // Returns hash and salt strings or an error if it fails. | 58 | // Returns hash and salt strings or an error if it fails. |
58 | func CreateHash(str, presalt string) (hash, salt string, err error) { | 59 | func CreateHash(str, presalt string) (hash, salt string, err error) { |
59 | // chech if message is presalted | 60 | // chech if message is presalted |
60 | if presalt == "" { | 61 | if presalt == "" { |
61 | salt, err = randomSalt() | 62 | salt, err = randomSalt() |
62 | if err != nil { | 63 | if err != nil { |
63 | return "", "", err | 64 | return "", "", err |
64 | } | 65 | } |
65 | } else { | 66 | } else { |
66 | salt = presalt | 67 | salt = presalt |
67 | } | 68 | } |
68 | 69 | ||
69 | // convert strings to raw byte slices | 70 | // convert strings to raw byte slices |
70 | rawstr := []byte(str) | 71 | rawstr := []byte(str) |
71 | rawsalt, err := hex.DecodeString(salt) | 72 | rawsalt, err := hex.DecodeString(salt) |
72 | if err != nil { | 73 | if err != nil { |
73 | return "", "", err | 74 | return "", "", err |
74 | } | 75 | } |
75 | 76 | ||
76 | rawdata := make([]byte, len(rawstr)+len(rawsalt)) | 77 | rawdata := make([]byte, len(rawstr)+len(rawsalt)) |
77 | rawdata = append(rawdata, rawstr...) | 78 | rawdata = append(rawdata, rawstr...) |
78 | rawdata = append(rawdata, rawsalt...) | 79 | rawdata = append(rawdata, rawsalt...) |
79 | 80 | ||
80 | // hash message + salt | 81 | // hash message + salt |
81 | hasher := sha256.New() | 82 | hasher := sha256.New() |
82 | hasher.Write(rawdata) | 83 | hasher.Write(rawdata) |
83 | rawhash := hasher.Sum(nil) | 84 | rawhash := hasher.Sum(nil) |
84 | 85 | ||
85 | hash = hex.EncodeToString(rawhash) | 86 | hash = hex.EncodeToString(rawhash) |
86 | return hash, salt, nil | 87 | return hash, salt, nil |
87 | } | 88 | } |
88 | 89 | ||
89 | // CreateAuthToken returns JWT token with encoded username, role, expiration date and issuer claims. | 90 | // CreateAuthToken returns JWT token with encoded username, role, expiration date and issuer claims. |
90 | // It returns an error if it fails. | 91 | // It returns an error if it fails. |
91 | func CreateAuthToken(username string, role Role) (TokenClaims, error) { | 92 | func CreateAuthToken(username string, role Role) (TokenClaims, error) { |
92 | t0 := (time.Now()).Unix() | 93 | t0 := (time.Now()).Unix() |
93 | t1 := (time.Now().Add(OneWeek)).Unix() | 94 | t1 := (time.Now().Add(OneWeek)).Unix() |
94 | claims := TokenClaims{ | 95 | claims := TokenClaims{ |
95 | TokenType: "Bearer", | 96 | TokenType: "Bearer", |
96 | Username: username, | 97 | Username: username, |
97 | Role: role.Name, | 98 | Role: role.Name, |
98 | RoleID: role.ID, | 99 | RoleID: role.ID, |
99 | ExpiresIn: t1 - t0, | 100 | ExpiresIn: t1 - t0, |
100 | } | 101 | } |
101 | // initialize jwt.StandardClaims fields (anonymous struct) | 102 | // initialize jwt.StandardClaims fields (anonymous struct) |
102 | claims.IssuedAt = t0 | 103 | claims.IssuedAt = t0 |
103 | claims.ExpiresAt = t1 | 104 | claims.ExpiresAt = t1 |
104 | claims.Issuer = appName | 105 | claims.Issuer = appName |
105 | 106 | ||
106 | jwtToken := jwt.NewWithClaims(jwt.SigningMethodHS256, claims) | 107 | jwtToken := jwt.NewWithClaims(jwt.SigningMethodHS256, claims) |
107 | token, err := jwtToken.SignedString([]byte(secret)) | 108 | token, err := jwtToken.SignedString([]byte(secret)) |
108 | if err != nil { | 109 | if err != nil { |
109 | return TokenClaims{}, err | 110 | return TokenClaims{}, err |
110 | } | 111 | } |
111 | claims.Token = token | 112 | claims.Token = token |
112 | return claims, nil | 113 | return claims, nil |
113 | } | 114 | } |
114 | 115 | ||
115 | // RefreshAuthToken returns new JWT token with sprolongs JWT token's expiration date for one week. | 116 | // RefreshAuthToken returns new JWT token with sprolongs JWT token's expiration date for one week. |
116 | // It returns new JWT token or an error if it fails. | 117 | // It returns new JWT token or an error if it fails. |
117 | func RefreshAuthToken(tok string) (TokenClaims, error) { | 118 | func RefreshAuthToken(tok string) (TokenClaims, error) { |
118 | token, err := jwt.ParseWithClaims(tok, &TokenClaims{}, secretFunc) | 119 | token, err := jwt.ParseWithClaims(tok, &TokenClaims{}, secretFunc) |
119 | if err != nil { | 120 | if err != nil { |
120 | if validation, ok := err.(*jwt.ValidationError); ok { | 121 | if validation, ok := err.(*jwt.ValidationError); ok { |
121 | // don't return error if token is expired | 122 | // don't return error if token is expired |
122 | // just extend it | 123 | // just extend it |
123 | if !(validation.Errors&jwt.ValidationErrorExpired != 0) { | 124 | if !(validation.Errors&jwt.ValidationErrorExpired != 0) { |
124 | return TokenClaims{}, err | 125 | return TokenClaims{}, err |
125 | } | 126 | } |
126 | } else { | 127 | } else { |
127 | return TokenClaims{}, err | 128 | return TokenClaims{}, err |
128 | } | 129 | } |
129 | } | 130 | } |
130 | 131 | ||
131 | // type assertion | 132 | // type assertion |
132 | claims, ok := token.Claims.(*TokenClaims) | 133 | claims, ok := token.Claims.(*TokenClaims) |
133 | if !ok { | 134 | if !ok { |
134 | return TokenClaims{}, errors.New("token is not valid") | 135 | return TokenClaims{}, errors.New("token is not valid") |
135 | } | 136 | } |
136 | 137 | ||
137 | // extend token expiration date | 138 | // extend token expiration date |
138 | return CreateAuthToken(claims.Username, Role{claims.Role, claims.RoleID}) | 139 | return CreateAuthToken(claims.Username, Role{claims.Role, claims.RoleID}) |
139 | } | 140 | } |
140 | 141 | ||
141 | // RbacCheck returns true if user that made HTTP request is authorized to | 142 | // RbacCheck returns true if user that made HTTP request is authorized to |
142 | // access the resource it is targeting. | 143 | // access the resource it is targeting. |
143 | // It exctracts user's role from the JWT token located in Authorization header of | 144 | // It exctracts user's role from the JWT token located in Authorization header of |
144 | // http.Request and then compares it with the list of supplied roles and returns | 145 | // http.Request and then compares it with the list of supplied roles and returns |
145 | // true if there's a match, if "*" is provided or if the authRoles is nil. | 146 | // true if there's a match, if "*" is provided or if the authRoles is nil. |
146 | // Otherwise it returns false. | 147 | // Otherwise it returns false. |
147 | func RbacCheck(req *http.Request, authRoles []string) bool { | 148 | func RbacCheck(req *http.Request, authRoles []string) bool { |
148 | if authRoles == nil { | 149 | if authRoles == nil { |
149 | return true | 150 | return true |
150 | } | 151 | } |
151 | 152 | ||
152 | // validate token and check expiration date | 153 | // validate token and check expiration date |
153 | claims, err := GetTokenClaims(req) | 154 | claims, err := GetTokenClaims(req) |
154 | if err != nil { | 155 | if err != nil { |
155 | return false | 156 | return false |
156 | } | 157 | } |
157 | // check if token has expired | 158 | // check if token has expired |
158 | if claims.ExpiresAt < (time.Now()).Unix() { | 159 | if claims.ExpiresAt < (time.Now()).Unix() { |
159 | return false | 160 | return false |
160 | } | 161 | } |
161 | 162 | ||
162 | // check if role extracted from token matches | 163 | // check if role extracted from token matches |
163 | // any of the provided (allowed) ones | 164 | // any of the provided (allowed) ones |
164 | for _, r := range authRoles { | 165 | for _, r := range authRoles { |
165 | if claims.Role == r || r == "*" { | 166 | if claims.Role == r || r == "*" { |
166 | return true | 167 | return true |
167 | } | 168 | } |
168 | } | 169 | } |
169 | 170 | ||
170 | return false | 171 | return false |
171 | } | 172 | } |
172 | 173 | ||
173 | // ProcessRBAC returns token claims and boolean value based on user's rights to access resource specified in req. | 174 | // ProcessRBAC returns token claims and boolean value based on user's rights to access resource specified in req. |
174 | // It exctracts user's role from the JWT token located in Authorization header of | 175 | // It exctracts user's role from the JWT token located in Authorization header of |
175 | // HTTP request and then compares it with the list of supplied (authorized); | 176 | // HTTP request and then compares it with the list of supplied (authorized); |
176 | // it returns true if there's a match, if "*" is provided or if the authRoles is nil. | 177 | // it returns true if there's a match, if "*" is provided or if the authRoles is nil. |
177 | func ProcessRBAC(req *http.Request, authRoles []string) (*TokenClaims, bool) { | 178 | func ProcessRBAC(req *http.Request, authRoles []string) (*TokenClaims, bool) { |
178 | if authRoles == nil { | 179 | if authRoles == nil { |
179 | return nil, true | 180 | return nil, true |
180 | } | 181 | } |
181 | 182 | ||
182 | // validate token and check expiration date | 183 | // validate token and check expiration date |
183 | claims, err := GetTokenClaims(req) | 184 | claims, err := GetTokenClaims(req) |
184 | if err != nil { | 185 | if err != nil { |
185 | return claims, false | 186 | return claims, false |
186 | } | 187 | } |
187 | // check if token has expired | 188 | // check if token has expired |
188 | if claims.ExpiresAt < (time.Now()).Unix() { | 189 | if claims.ExpiresAt < (time.Now()).Unix() { |
189 | return claims, false | 190 | return claims, false |
190 | } | 191 | } |
191 | 192 | ||
192 | // check if role extracted from token matches | 193 | // check if role extracted from token matches |
193 | // any of the provided (allowed) ones | 194 | // any of the provided (allowed) ones |
194 | for _, r := range authRoles { | 195 | for _, r := range authRoles { |
195 | if claims.Role == r || r == "*" { | 196 | if claims.Role == r || r == "*" { |
196 | return claims, true | 197 | return claims, true |
197 | } | 198 | } |
198 | } | 199 | } |
199 | 200 | ||
200 | return claims, false | 201 | return claims, false |
201 | } | 202 | } |
202 | 203 | ||
203 | // GetTokenClaims extracts JWT claims from Authorization header of the request. | 204 | // GetTokenClaims extracts JWT claims from Authorization header of the request. |
204 | // Returns token claims or an error. | 205 | // Returns token claims or an error. |
205 | func GetTokenClaims(req *http.Request) (*TokenClaims, error) { | 206 | func GetTokenClaims(req *http.Request) (*TokenClaims, error) { |
206 | // check for and strip 'Bearer' prefix | 207 | // check for and strip 'Bearer' prefix |
207 | var tokstr string | 208 | var tokstr string |
208 | authHead := req.Header.Get("Authorization") | 209 | authHead := req.Header.Get("Authorization") |
209 | if ok := strings.HasPrefix(authHead, "Bearer "); ok { | 210 | if ok := strings.HasPrefix(authHead, "Bearer "); ok { |
210 | tokstr = strings.TrimPrefix(authHead, "Bearer ") | 211 | tokstr = strings.TrimPrefix(authHead, "Bearer ") |
211 | } else { | 212 | } else { |
212 | return &TokenClaims{}, errors.New("authorization header in incomplete") | 213 | return &TokenClaims{}, errors.New("authorization header in incomplete") |
213 | } | 214 | } |
214 | 215 | ||
215 | token, err := jwt.ParseWithClaims(tokstr, &TokenClaims{}, secretFunc) | 216 | token, err := jwt.ParseWithClaims(tokstr, &TokenClaims{}, secretFunc) |
216 | if err != nil { | 217 | if err != nil { |
217 | return &TokenClaims{}, err | 218 | return &TokenClaims{}, err |
218 | } | 219 | } |
219 | 220 | ||
220 | // type assertion | 221 | // type assertion |
221 | claims, ok := token.Claims.(*TokenClaims) | 222 | claims, ok := token.Claims.(*TokenClaims) |
222 | if !ok || !token.Valid { | 223 | if !ok || !token.Valid { |
223 | return &TokenClaims{}, errors.New("token is not valid") | 224 | return &TokenClaims{}, errors.New("token is not valid") |
224 | } | 225 | } |
225 | 226 | ||
226 | return claims, nil | 227 | return claims, nil |
227 | } | 228 | } |
228 | 229 | ||
229 | // randomSalt returns a string of random characters of 'saltSize' length. | 230 | // randomSalt returns a string of random characters of 'saltSize' length. |
230 | func randomSalt() (s string, err error) { | 231 | func randomSalt() (s string, err error) { |
231 | rawsalt := make([]byte, saltSize) | 232 | rawsalt := make([]byte, saltSize) |
232 | 233 | ||
233 | _, err = rand.Read(rawsalt) | 234 | _, err = rand.Read(rawsalt) |
234 | if err != nil { | 235 | if err != nil { |
235 | return "", err | 236 | return "", err |
236 | } | 237 | } |
237 | 238 | ||
238 | s = hex.EncodeToString(rawsalt) | 239 | s = hex.EncodeToString(rawsalt) |
239 | return s, nil | 240 | return s, nil |
240 | } | 241 | } |
241 | 242 | ||
242 | // secretFunc returns byte slice of API secret keyword. | 243 | // secretFunc returns byte slice of API secret keyword. |
243 | func secretFunc(token *jwt.Token) (interface{}, error) { | 244 | func secretFunc(token *jwt.Token) (interface{}, error) { |
format_utility.go
1 | package webutility | 1 | package webutility |
2 | 2 | ||
3 | import ( | 3 | import ( |
4 | "time" | ||
5 | "fmt" | 4 | "fmt" |
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 := date.(time.Time) | 17 | t, ok := date.(time.Time) |
18 | if !ok { | ||
19 | return 0 | ||
20 | } | ||
18 | return t.Unix() | 21 | return t.Unix() |
19 | 22 | ||
20 | } | 23 | } |
21 | return 0 | 24 | return 0 |
22 | } | 25 | } |
23 | 26 | ||
24 | // EqualQuotes encapsulates given string in SQL 'equal' statement and returns result. | 27 | // EqualQuotes encapsulates given string in SQL 'equal' statement and returns result. |
25 | // Example: "hello" -> " = 'hello'" | 28 | // Example: "hello" -> " = 'hello'" |
26 | func EqualQuotes(stmt string) string { | 29 | func EqualQuotes(stmt string) string { |
27 | if stmt != "" { | 30 | if stmt != "" { |
28 | stmt = fmt.Sprintf(" = '%s'", stmt) | 31 | stmt = fmt.Sprintf(" = '%s'", stmt) |
29 | } | 32 | } |
30 | return stmt | 33 | return stmt |
31 | } | 34 | } |
32 | 35 | ||
33 | func EqualString(stmt string) string { | 36 | func EqualString(stmt string) string { |
34 | if stmt != "" { | 37 | if stmt != "" { |
35 | stmt = fmt.Sprintf(" = %s", stmt) | 38 | stmt = fmt.Sprintf(" = %s", stmt) |
36 | } | 39 | } |
37 | return stmt | 40 | return stmt |
38 | } | 41 | } |
39 | 42 | ||
40 | // LikeQuotes encapsulates given string in SQL 'like' statement and returns result. | 43 | // LikeQuotes encapsulates given string in SQL 'like' statement and returns result. |
41 | // Example: "hello" -> " LIKE UPPER('%hello%')" | 44 | // Example: "hello" -> " LIKE UPPER('%hello%')" |
42 | func LikeQuotes(stmt string) string { | 45 | func LikeQuotes(stmt string) string { |
43 | if stmt != "" { | 46 | if stmt != "" { |
44 | stmt = fmt.Sprintf(" LIKE UPPER('%s%s%s')", "%", stmt, "%") | 47 | stmt = fmt.Sprintf(" LIKE UPPER('%s%s%s')", "%", stmt, "%") |
45 | } | 48 | } |
46 | return stmt | 49 | return stmt |
47 | } | 50 | } |
http_utility.go
1 | package webutility | 1 | package webutility |
2 | 2 | ||
3 | import ( | 3 | import ( |
4 | "encoding/json" | 4 | "encoding/json" |
5 | "net/http" | 5 | "net/http" |
6 | ) | 6 | ) |
7 | 7 | ||
8 | type webError struct { | 8 | type webError struct { |
9 | Request string `json:"request"` | 9 | Request string `json:"request"` |
10 | Error string `json:"error"` | 10 | Error string `json:"error"` |
11 | } | 11 | } |
12 | 12 | ||
13 | // NotFoundHandler writes HTTP error 404 to w. | 13 | // NotFoundHandler writes HTTP error 404 to w. |
14 | func NotFoundHandler(w http.ResponseWriter, req *http.Request) { | 14 | func NotFoundHandler(w http.ResponseWriter, req *http.Request) { |
15 | SetDefaultHeaders(w) | 15 | SetDefaultHeaders(w) |
16 | if req.Method == "OPTIONS" { | 16 | if req.Method == "OPTIONS" { |
17 | return | 17 | return |
18 | } | 18 | } |
19 | NotFound(w, req, "Not found") | 19 | NotFound(w, req, "Not found") |
20 | } | 20 | } |
21 | 21 | ||
22 | // SetDefaultHeaders set's default headers for an HTTP response. | 22 | // SetDefaultHeaders set's default headers for an HTTP response. |
23 | func SetDefaultHeaders(w http.ResponseWriter) { | 23 | func SetDefaultHeaders(w http.ResponseWriter) { |
24 | w.Header().Set("Access-Control-Allow-Origin", "*") | 24 | w.Header().Set("Access-Control-Allow-Origin", "*") |
25 | w.Header().Set("Access-Control-Allow-Methods", "POST, GET, PUT, DELETE, OPTIONS") | 25 | w.Header().Set("Access-Control-Allow-Methods", "POST, GET, PUT, DELETE, OPTIONS") |
26 | w.Header().Set("Access-Control-Allow-Headers", `Accept, Content-Type, | 26 | w.Header().Set("Access-Control-Allow-Headers", `Accept, Content-Type, |
27 | Content-Length, Accept-Encoding, X-CSRF-Token, Authorization`) | 27 | Content-Length, Accept-Encoding, X-CSRF-Token, Authorization`) |
28 | w.Header().Set("Content-Type", "application/json; charset=utf-8") | 28 | w.Header().Set("Content-Type", "application/json; charset=utf-8") |
29 | } | 29 | } |
30 | 30 | ||
31 | func ReqLocale(req *http.Request, dflt string) string { | ||
32 | loc := req.FormValue("locale") | ||
33 | if loc == "" { | ||
34 | return dflt | ||
35 | } | ||
36 | return loc | ||
37 | } | ||
38 | |||
31 | // 2xx | 39 | // 2xx |
32 | func Success(w http.ResponseWriter, payload *Payload, code int) { | 40 | func Success(w http.ResponseWriter, payload *Payload, code int) { |
33 | w.WriteHeader(code) | 41 | w.WriteHeader(code) |
34 | if payload != nil { | 42 | if payload != nil { |
35 | json.NewEncoder(w).Encode(*payload) | 43 | json.NewEncoder(w).Encode(*payload) |
36 | } | 44 | } |
37 | } | 45 | } |
38 | 46 | ||
39 | // 200 | 47 | // 200 |
40 | func OK(w http.ResponseWriter, payload *Payload) { | 48 | func OK(w http.ResponseWriter, payload *Payload) { |
41 | Success(w, payload, http.StatusOK) | 49 | Success(w, payload, http.StatusOK) |
42 | } | 50 | } |
43 | 51 | ||
44 | // 201 | 52 | // 201 |
45 | func Created(w http.ResponseWriter, payload *Payload) { | 53 | func Created(w http.ResponseWriter, payload *Payload) { |
46 | Success(w, payload, http.StatusCreated) | 54 | Success(w, payload, http.StatusCreated) |
47 | } | 55 | } |
48 | 56 | ||
49 | // 4xx; 5xx | 57 | // 4xx; 5xx |
50 | func Error(w http.ResponseWriter, r *http.Request, code int, err string) { | 58 | func Error(w http.ResponseWriter, r *http.Request, code int, err string) { |
51 | werr := webError{Error: err, Request: r.Method + " " + r.RequestURI} | 59 | werr := webError{Error: err, Request: r.Method + " " + r.RequestURI} |
52 | w.WriteHeader(code) | 60 | w.WriteHeader(code) |
53 | json.NewEncoder(w).Encode(werr) | 61 | json.NewEncoder(w).Encode(werr) |
54 | } | 62 | } |
55 | 63 | ||
56 | // 400 | 64 | // 400 |
57 | func BadRequest(w http.ResponseWriter, r *http.Request, err string) { | 65 | func BadRequest(w http.ResponseWriter, r *http.Request, err string) { |
58 | Error(w, r, http.StatusBadRequest, err) | 66 | Error(w, r, http.StatusBadRequest, err) |
59 | } | 67 | } |
60 | 68 | ||
61 | // 404 | 69 | // 404 |
62 | func NotFound(w http.ResponseWriter, r *http.Request, err string) { | 70 | func NotFound(w http.ResponseWriter, r *http.Request, err string) { |
63 | Error(w, r, http.StatusNotFound, err) | 71 | Error(w, r, http.StatusNotFound, err) |
64 | } | 72 | } |
65 | 73 | ||
66 | // 401 | 74 | // 401 |
67 | func Unauthorized(w http.ResponseWriter, r *http.Request, err string) { | 75 | func Unauthorized(w http.ResponseWriter, r *http.Request, err string) { |
68 | Error(w, r, http.StatusUnauthorized, err) | 76 | Error(w, r, http.StatusUnauthorized, err) |
69 | } | 77 | } |
70 | 78 | ||
71 | // 403 | 79 | // 403 |
72 | func Forbidden(w http.ResponseWriter, r *http.Request, err string) { | 80 | func Forbidden(w http.ResponseWriter, r *http.Request, err string) { |
73 | Error(w, r, http.StatusForbidden, err) | 81 | Error(w, r, http.StatusForbidden, err) |
74 | } | 82 | } |
75 | 83 | ||
76 | // 403 | 84 | // 403 |
77 | func Conflict(w http.ResponseWriter, r *http.Request, err string) { | 85 | func Conflict(w http.ResponseWriter, r *http.Request, err string) { |
78 | Error(w, r, http.StatusConflict, err) | 86 | Error(w, r, http.StatusConflict, err) |
79 | } | 87 | } |
80 | 88 | ||
81 | // 500 | 89 | // 500 |
82 | func InternalServerError(w http.ResponseWriter, r *http.Request, err string) { | 90 | func InternalServerError(w http.ResponseWriter, r *http.Request, err string) { |
83 | Error(w, r, http.StatusInternalServerError, err) | 91 | Error(w, r, http.StatusInternalServerError, err) |
84 | } | 92 | } |
85 | 93 | ||
86 | /// | 94 | /// |
87 | /// Old API | 95 | /// Old API |
88 | /// | 96 | /// |
89 | 97 | ||
90 | const ( | 98 | const ( |
91 | templateHttpErr500_EN = "An internal server error has occurred." | 99 | templateHttpErr500_EN = "An internal server error has occurred." |
92 | templateHttpErr500_RS = "Došlo je do greške na serveru." | 100 | templateHttpErr500_RS = "Došlo je do greške na serveru." |
93 | templateHttpErr400_EN = "Bad request." | 101 | templateHttpErr400_EN = "Bad request." |
94 | templateHttpErr400_RS = "Neispravan zahtev." | 102 | templateHttpErr400_RS = "Neispravan zahtev." |
95 | templateHttpErr404_EN = "Resource not found." | 103 | templateHttpErr404_EN = "Resource not found." |
96 | templateHttpErr404_RS = "Resurs nije pronadjen." | 104 | templateHttpErr404_RS = "Resurs nije pronadjen." |
97 | templateHttpErr401_EN = "Unauthorized request." | 105 | templateHttpErr401_EN = "Unauthorized request." |
98 | templateHttpErr401_RS = "Neautorizovan zahtev." | 106 | templateHttpErr401_RS = "Neautorizovan zahtev." |
99 | ) | 107 | ) |
100 | 108 | ||
101 | type httpError struct { | 109 | type httpError struct { |
102 | Error []HttpErrorDesc `json:"error"` | 110 | Error []HttpErrorDesc `json:"error"` |
103 | Request string `json:"request"` | 111 | Request string `json:"request"` |
104 | } | 112 | } |
105 | 113 | ||
106 | type HttpErrorDesc struct { | 114 | type HttpErrorDesc struct { |
107 | Lang string `json:"lang"` | 115 | Lang string `json:"lang"` |
108 | Desc string `json:"description"` | 116 | Desc string `json:"description"` |
109 | } | 117 | } |
110 | 118 | ||
111 | // DeliverPayload encodes payload as JSON to w. | 119 | // DeliverPayload encodes payload as JSON to w. |
112 | func DeliverPayload(w http.ResponseWriter, payload Payload) { | 120 | func DeliverPayload(w http.ResponseWriter, payload Payload) { |
113 | // Don't write status OK in the headers here. Leave it up for the caller. | 121 | // Don't write status OK in the headers here. Leave it up for the caller. |
114 | // E.g. Status 201. | 122 | // E.g. Status 201. |
115 | json.NewEncoder(w).Encode(payload) | 123 | json.NewEncoder(w).Encode(payload) |
116 | payload.Data = nil | 124 | payload.Data = nil |
117 | } | 125 | } |
118 | 126 | ||
119 | // ErrorResponse writes HTTP error to w. | 127 | // ErrorResponse writes HTTP error to w. |
120 | func ErrorResponse(w http.ResponseWriter, r *http.Request, code int, desc []HttpErrorDesc) { | 128 | func ErrorResponse(w http.ResponseWriter, r *http.Request, code int, desc []HttpErrorDesc) { |
121 | err := httpError{desc, r.Method + " " + r.RequestURI} | 129 | err := httpError{desc, r.Method + " " + r.RequestURI} |
122 | w.WriteHeader(code) | 130 | w.WriteHeader(code) |
123 | json.NewEncoder(w).Encode(err) | 131 | json.NewEncoder(w).Encode(err) |
124 | } | 132 | } |
125 | 133 | ||
126 | // NotFoundResponse writes HTTP error 404 to w. | 134 | // NotFoundResponse writes HTTP error 404 to w. |
127 | func NotFoundResponse(w http.ResponseWriter, req *http.Request) { | 135 | func NotFoundResponse(w http.ResponseWriter, req *http.Request) { |
128 | ErrorResponse(w, req, http.StatusNotFound, []HttpErrorDesc{ | 136 | ErrorResponse(w, req, http.StatusNotFound, []HttpErrorDesc{ |
129 | {"en", templateHttpErr404_EN}, | 137 | {"en", templateHttpErr404_EN}, |
130 | {"rs", templateHttpErr404_RS}, | 138 | {"rs", templateHttpErr404_RS}, |
131 | }) | 139 | }) |
132 | } | 140 | } |
133 | 141 | ||
134 | // BadRequestResponse writes HTTP error 400 to w. | 142 | // BadRequestResponse writes HTTP error 400 to w. |
135 | func BadRequestResponse(w http.ResponseWriter, req *http.Request) { | 143 | func BadRequestResponse(w http.ResponseWriter, req *http.Request) { |
136 | ErrorResponse(w, req, http.StatusBadRequest, []HttpErrorDesc{ | 144 | ErrorResponse(w, req, http.StatusBadRequest, []HttpErrorDesc{ |
137 | {"en", templateHttpErr400_EN}, | 145 | {"en", templateHttpErr400_EN}, |
138 | {"rs", templateHttpErr400_RS}, | 146 | {"rs", templateHttpErr400_RS}, |
139 | }) | 147 | }) |
140 | } | 148 | } |
141 | 149 | ||
142 | // InternalSeverErrorResponse writes HTTP error 500 to w. | 150 | // InternalSeverErrorResponse writes HTTP error 500 to w. |
143 | func InternalServerErrorResponse(w http.ResponseWriter, req *http.Request) { | 151 | func InternalServerErrorResponse(w http.ResponseWriter, req *http.Request) { |
144 | ErrorResponse(w, req, http.StatusInternalServerError, []HttpErrorDesc{ | 152 | ErrorResponse(w, req, http.StatusInternalServerError, []HttpErrorDesc{ |
145 | {"en", templateHttpErr500_EN}, | 153 | {"en", templateHttpErr500_EN}, |
146 | {"rs", templateHttpErr500_RS}, | 154 | {"rs", templateHttpErr500_RS}, |
147 | }) | 155 | }) |
148 | } | 156 | } |
149 | 157 | ||
150 | // UnauthorizedError writes HTTP error 401 to w. | 158 | // UnauthorizedError writes HTTP error 401 to w. |
151 | func UnauthorizedResponse(w http.ResponseWriter, req *http.Request) { | 159 | func UnauthorizedResponse(w http.ResponseWriter, req *http.Request) { |
152 | w.Header().Set("WWW-Authenticate", "Bearer") | 160 | w.Header().Set("WWW-Authenticate", "Bearer") |
153 | ErrorResponse(w, req, http.StatusUnauthorized, []HttpErrorDesc{ | 161 | ErrorResponse(w, req, http.StatusUnauthorized, []HttpErrorDesc{ |
154 | {"en", templateHttpErr401_EN}, | 162 | {"en", templateHttpErr401_EN}, |
155 | {"rs", templateHttpErr401_RS}, | 163 | {"rs", templateHttpErr401_RS}, |
156 | }) | 164 | }) |
157 | } | 165 | } |
158 | 166 |
json_utility.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 | "sync" | 10 | "sync" |
11 | "time" | 11 | "time" |
12 | 12 | ||
13 | "git.to-net.rs/marko.tikvic/gologger" | 13 | "git.to-net.rs/marko.tikvic/gologger" |
14 | ) | 14 | ) |
15 | 15 | ||
16 | var ( | 16 | var ( |
17 | mu = &sync.Mutex{} | 17 | mu = &sync.Mutex{} |
18 | metadata = make(map[string]Payload) | 18 | metadata = make(map[string]Payload) |
19 | updateQue = make(map[string][]byte) | 19 | updateQue = make(map[string][]byte) |
20 | 20 | ||
21 | metadataDB *sql.DB | 21 | metadataDB *sql.DB |
22 | activeProject string | 22 | activeProject string |
23 | 23 | ||
24 | inited bool | 24 | inited bool |
25 | driver string | 25 | driver string |
26 | ) | 26 | ) |
27 | 27 | ||
28 | var logger *gologger.Logger | 28 | var logger *gologger.Logger |
29 | 29 | ||
30 | func init() { | 30 | func init() { |
31 | var err error | 31 | var err error |
32 | logger, err = gologger.New("metadata", gologger.MaxLogSize100KB) | 32 | logger, err = gologger.New("webutility", gologger.MaxLogSize100KB) |
33 | if err != nil { | 33 | if err != nil { |
34 | fmt.Printf("webutility: %s\n", err.Error()) | 34 | fmt.Printf("webutility: %s\n", err.Error()) |
35 | } | 35 | } |
36 | } | 36 | } |
37 | 37 | ||
38 | type LangMap map[string]map[string]string | 38 | type LangMap map[string]map[string]string |
39 | 39 | ||
40 | type Field struct { | 40 | type Field struct { |
41 | Parameter string `json:"param"` | 41 | Parameter string `json:"param"` |
42 | Type string `json:"type"` | 42 | Type string `json:"type"` |
43 | Visible bool `json:"visible"` | 43 | Visible bool `json:"visible"` |
44 | Editable bool `json:"editable"` | 44 | Editable bool `json:"editable"` |
45 | } | 45 | } |
46 | 46 | ||
47 | type CorrelationField struct { | 47 | type CorrelationField struct { |
48 | Result string `json:"result"` | 48 | Result string `json:"result"` |
49 | Elements []string `json:"elements"` | 49 | Elements []string `json:"elements"` |
50 | Type string `json:"type"` | 50 | Type string `json:"type"` |
51 | } | 51 | } |
52 | 52 | ||
53 | type Translation struct { | 53 | type Translation struct { |
54 | Language string `json:"language"` | 54 | Language string `json:"language"` |
55 | FieldsLabels map[string]string `json:"fieldsLabels"` | 55 | FieldsLabels map[string]string `json:"fieldsLabels"` |
56 | } | 56 | } |
57 | 57 | ||
58 | type Payload struct { | 58 | type Payload struct { |
59 | Method string `json:"method"` | 59 | Method string `json:"method"` |
60 | Params map[string]string `json:"params"` | 60 | Params map[string]string `json:"params"` |
61 | Lang []Translation `json:"lang"` | 61 | Lang []Translation `json:"lang"` |
62 | Fields []Field `json:"fields"` | 62 | Fields []Field `json:"fields"` |
63 | Correlations []CorrelationField `json:"correlationFields"` | 63 | Correlations []CorrelationField `json:"correlationFields"` |
64 | IdField string `json:"idField"` | 64 | IdField string `json:"idField"` |
65 | 65 | ||
66 | // Data holds JSON payload. | 66 | // Data holds JSON payload. |
67 | // It can't be used for itteration. | 67 | // It can't be used for itteration. |
68 | Data interface{} `json:"data"` | 68 | Data interface{} `json:"data"` |
69 | } | 69 | } |
70 | 70 | ||
71 | // NewPayload returs a payload sceleton for entity described with etype. | 71 | // NewPayload returs a payload sceleton for entity described with etype. |
72 | func NewPayload(r *http.Request, etype string) Payload { | 72 | func NewPayload(r *http.Request, etype string) Payload { |
73 | pload := metadata[etype] | 73 | pload := metadata[etype] |
74 | pload.Method = r.Method + " " + r.RequestURI | 74 | pload.Method = r.Method + " " + r.RequestURI |
75 | return pload | 75 | return pload |
76 | } | 76 | } |
77 | 77 | ||
78 | // DecodeJSON decodes JSON data from r to v. | 78 | // DecodeJSON decodes JSON data from r to v. |
79 | // Returns an error if it fails. | 79 | // Returns an error if it fails. |
80 | func DecodeJSON(r io.Reader, v interface{}) error { | 80 | func DecodeJSON(r io.Reader, v interface{}) error { |
81 | return json.NewDecoder(r).Decode(v) | 81 | return json.NewDecoder(r).Decode(v) |
82 | } | 82 | } |
83 | 83 | ||
84 | // InitPayloadsMetadata loads all payloads' information into 'metadata' variable. | 84 | // InitPayloadsMetadata loads all payloads' information into 'metadata' variable. |
85 | func InitPayloadsMetadata(drv string, db *sql.DB, project string) error { | 85 | func InitPayloadsMetadata(drv string, db *sql.DB, project string) error { |
86 | if drv != "ora" && drv != "mysql" { | 86 | if drv != "ora" && drv != "mysql" { |
87 | return errors.New("driver not supported") | 87 | return errors.New("driver not supported") |
88 | } | 88 | } |
89 | driver = drv | 89 | driver = drv |
90 | metadataDB = db | 90 | metadataDB = db |
91 | activeProject = project | 91 | activeProject = project |
92 | 92 | ||
93 | mu.Lock() | 93 | mu.Lock() |
94 | defer mu.Unlock() | 94 | defer mu.Unlock() |
95 | err := initMetadata(project) | 95 | err := initMetadata(project) |
96 | if err != nil { | 96 | if err != nil { |
97 | return err | 97 | return err |
98 | } | 98 | } |
99 | inited = true | 99 | inited = true |
100 | 100 | ||
101 | return nil | 101 | return nil |
102 | } | 102 | } |
103 | 103 | ||
104 | func EnableHotloading(interval int) { | 104 | func EnableHotloading(interval int) { |
105 | if interval > 0 { | 105 | if interval > 0 { |
106 | go hotload(interval) | 106 | go hotload(interval) |
107 | } | 107 | } |
108 | } | 108 | } |
109 | 109 | ||
110 | func GetMetadataForAllEntities() map[string]Payload { | 110 | func GetMetadataForAllEntities() map[string]Payload { |
111 | return metadata | 111 | return metadata |
112 | } | 112 | } |
113 | 113 | ||
114 | func GetMetadataForEntity(t string) (Payload, bool) { | 114 | func GetMetadataForEntity(t string) (Payload, bool) { |
115 | p, ok := metadata[t] | 115 | p, ok := metadata[t] |
116 | return p, ok | 116 | return p, ok |
117 | } | 117 | } |
118 | 118 | ||
119 | func QueEntityModelUpdate(entityType string, v interface{}) { | 119 | func QueEntityModelUpdate(entityType string, v interface{}) { |
120 | updateQue[entityType], _ = json.Marshal(v) | 120 | updateQue[entityType], _ = json.Marshal(v) |
121 | } | 121 | } |
122 | 122 | ||
123 | func initMetadata(project string) error { | 123 | func initMetadata(project string) error { |
124 | rows, err := metadataDB.Query(`select | 124 | rows, err := metadataDB.Query(`select |
125 | entity_type, | 125 | entity_type, |
126 | metadata | 126 | metadata |
127 | from entities | 127 | from entities |
128 | where projekat = ` + fmt.Sprintf("'%s'", project)) | 128 | where projekat = ` + fmt.Sprintf("'%s'", project)) |
129 | if err != nil { | 129 | if err != nil { |
130 | return err | 130 | return err |
131 | } | 131 | } |
132 | defer rows.Close() | 132 | defer rows.Close() |
133 | 133 | ||
134 | count := 0 | 134 | count := 0 |
135 | success := 0 | 135 | success := 0 |
136 | if len(metadata) > 0 { | 136 | if len(metadata) > 0 { |
137 | metadata = nil | 137 | metadata = nil |
138 | } | 138 | } |
139 | metadata = make(map[string]Payload) | 139 | metadata = make(map[string]Payload) |
140 | for rows.Next() { | 140 | for rows.Next() { |
141 | var name, load string | 141 | var name, load string |
142 | rows.Scan(&name, &load) | 142 | rows.Scan(&name, &load) |
143 | 143 | ||
144 | p := Payload{} | 144 | p := Payload{} |
145 | err := json.Unmarshal([]byte(load), &p) | 145 | err := json.Unmarshal([]byte(load), &p) |
146 | if err != nil { | 146 | if err != nil { |
147 | logger.Log("webutility: couldn't init: '%s' metadata: %s\n%s\n", name, err.Error(), load) | 147 | logger.Log("webutility: couldn't init: '%s' metadata: %s\n%s\n", name, err.Error(), load) |
148 | } else { | 148 | } else { |
149 | success++ | 149 | success++ |
150 | metadata[name] = p | 150 | metadata[name] = p |
151 | } | 151 | } |
152 | count++ | 152 | count++ |
153 | } | 153 | } |
154 | perc := float32(success/count) * 100.0 | 154 | perc := float32(success/count) * 100.0 |
155 | logger.Log("webutility: loaded %d/%d (%.1f%%) entities\n", success, count, perc) | 155 | logger.Log("webutility: loaded %d/%d (%.1f%%) entities\n", success, count, perc) |
156 | 156 | ||
157 | return nil | 157 | return nil |
158 | } | 158 | } |
159 | 159 | ||
160 | func hotload(n int) { | 160 | func hotload(n int) { |
161 | entityScan := make(map[string]int64) | 161 | entityScan := make(map[string]int64) |
162 | firstCheck := true | 162 | firstCheck := true |
163 | for { | 163 | for { |
164 | time.Sleep(time.Duration(n) * time.Second) | 164 | time.Sleep(time.Duration(n) * time.Second) |
165 | rows, err := metadataDB.Query(`select | 165 | rows, err := metadataDB.Query(`select |
166 | ora_rowscn, | 166 | ora_rowscn, |
167 | entity_type | 167 | entity_type |
168 | from entities where projekat = ` + fmt.Sprintf("'%s'", activeProject)) | 168 | from entities where projekat = ` + fmt.Sprintf("'%s'", activeProject)) |
169 | if err != nil { | 169 | if err != nil { |
170 | logger.Log("webutility: hotload failed: %v\n", err) | 170 | logger.Log("webutility: hotload failed: %v\n", err) |
171 | time.Sleep(time.Duration(n) * time.Second) | 171 | time.Sleep(time.Duration(n) * time.Second) |
172 | continue | 172 | continue |
173 | } | 173 | } |
174 | 174 | ||
175 | var toRefresh []string | 175 | var toRefresh []string |
176 | for rows.Next() { | 176 | for rows.Next() { |
177 | var scanID int64 | 177 | var scanID int64 |
178 | var entity string | 178 | var entity string |
179 | rows.Scan(&scanID, &entity) | 179 | rows.Scan(&scanID, &entity) |
180 | oldID, ok := entityScan[entity] | 180 | oldID, ok := entityScan[entity] |
181 | if !ok || oldID != scanID { | 181 | if !ok || oldID != scanID { |
182 | entityScan[entity] = scanID | 182 | entityScan[entity] = scanID |
183 | toRefresh = append(toRefresh, entity) | 183 | toRefresh = append(toRefresh, entity) |
184 | } | 184 | } |
185 | } | 185 | } |
186 | rows.Close() | 186 | rows.Close() |
187 | 187 | ||
188 | if rows.Err() != nil { | 188 | if rows.Err() != nil { |
189 | logger.Log("webutility: hotload rset error: %v\n", rows.Err()) | 189 | logger.Log("webutility: hotload rset error: %v\n", rows.Err()) |
190 | time.Sleep(time.Duration(n) * time.Second) | 190 | time.Sleep(time.Duration(n) * time.Second) |
191 | continue | 191 | continue |
192 | } | 192 | } |
193 | 193 | ||
194 | if len(toRefresh) > 0 && !firstCheck { | 194 | if len(toRefresh) > 0 && !firstCheck { |
195 | mu.Lock() | 195 | mu.Lock() |
196 | refreshMetadata(toRefresh) | 196 | refreshMetadata(toRefresh) |
197 | mu.Unlock() | 197 | mu.Unlock() |
198 | } | 198 | } |
199 | if firstCheck { | 199 | if firstCheck { |
200 | firstCheck = false | 200 | firstCheck = false |
201 | } | 201 | } |
202 | } | 202 | } |
203 | } | 203 | } |
204 | 204 | ||
205 | func refreshMetadata(entities []string) { | 205 | func refreshMetadata(entities []string) { |
206 | for _, e := range entities { | 206 | for _, e := range entities { |
207 | fmt.Printf("refreshing %s\n", e) | 207 | fmt.Printf("refreshing %s\n", e) |
208 | rows, err := metadataDB.Query(`select | 208 | rows, err := metadataDB.Query(`select |
209 | metadata | 209 | metadata |
210 | from entities | 210 | from entities |
211 | where projekat = ` + fmt.Sprintf("'%s'", activeProject) + | 211 | where projekat = ` + fmt.Sprintf("'%s'", activeProject) + |
212 | ` and entity_type = ` + fmt.Sprintf("'%s'", e)) | 212 | ` and entity_type = ` + fmt.Sprintf("'%s'", e)) |
213 | 213 | ||
214 | if err != nil { | 214 | if err != nil { |
215 | logger.Log("webutility: refresh: prep: %v\n", err) | 215 | logger.Log("webutility: refresh: prep: %v\n", err) |
216 | rows.Close() | 216 | rows.Close() |
217 | continue | 217 | continue |
218 | } | 218 | } |
219 | 219 | ||
220 | for rows.Next() { | 220 | for rows.Next() { |
221 | var load string | 221 | var load string |
222 | rows.Scan(&load) | 222 | rows.Scan(&load) |
223 | p := Payload{} | 223 | p := Payload{} |
224 | err := json.Unmarshal([]byte(load), &p) | 224 | err := json.Unmarshal([]byte(load), &p) |
225 | if err != nil { | 225 | if err != nil { |
226 | logger.Log("webutility: couldn't refresh: '%s' metadata: %s\n%s\n", e, err.Error(), load) | 226 | logger.Log("webutility: couldn't refresh: '%s' metadata: %s\n%s\n", e, err.Error(), load) |
227 | } else { | 227 | } else { |
228 | metadata[e] = p | 228 | metadata[e] = p |
229 | } | 229 | } |
230 | } | 230 | } |
231 | rows.Close() | 231 | rows.Close() |
232 | } | 232 | } |
233 | } | 233 | } |
234 | 234 | ||
235 | func UpdateEntityModels(command string) (total, upd, add int, err error) { | 235 | func UpdateEntityModels(command string) (total, upd, add int, err error) { |
236 | if command != "force" && command != "missing" { | 236 | if command != "force" && command != "missing" { |
237 | return total, 0, 0, errors.New("webutility: unknown command: " + command) | 237 | return total, 0, 0, errors.New("webutility: unknown command: " + command) |
238 | } | 238 | } |
239 | 239 | ||
240 | if !inited { | 240 | if !inited { |
241 | return 0, 0, 0, errors.New("webutility: metadata not initialized but update was tried.") | 241 | return 0, 0, 0, errors.New("webutility: metadata not initialized but update was tried.") |
242 | } | 242 | } |
243 | 243 | ||
244 | total = len(updateQue) | 244 | total = len(updateQue) |
245 | 245 | ||
246 | forUpdate := make([]string, 0) | 246 | forUpdate := make([]string, 0) |
247 | forAdd := make([]string, 0) | 247 | forAdd := make([]string, 0) |
248 | 248 | ||
249 | for k, _ := range updateQue { | 249 | for k, _ := range updateQue { |
250 | if _, exists := metadata[k]; exists { | 250 | if _, exists := metadata[k]; exists { |
251 | if command == "force" { | 251 | if command == "force" { |
252 | forUpdate = append(forUpdate, k) | 252 | forUpdate = append(forUpdate, k) |
253 | } | 253 | } |
254 | } else { | 254 | } else { |
255 | forAdd = append(forAdd, k) | 255 | forAdd = append(forAdd, k) |
256 | } | 256 | } |
257 | } | 257 | } |
258 | 258 | ||
259 | var uStmt *sql.Stmt | 259 | var uStmt *sql.Stmt |
260 | if driver == "ora" { | 260 | if driver == "ora" { |
261 | uStmt, err = metadataDB.Prepare("update entities set entity_model = :1 where entity_type = :2") | 261 | uStmt, err = metadataDB.Prepare("update entities set entity_model = :1 where entity_type = :2") |
262 | if err != nil { | 262 | if err != nil { |
263 | return | 263 | return |
264 | } | 264 | } |
265 | } else if driver == "mysql" { | 265 | } else if driver == "mysql" { |
266 | uStmt, err = metadataDB.Prepare("update entities set entity_model = ? where entity_type = ?") | 266 | uStmt, err = metadataDB.Prepare("update entities set entity_model = ? where entity_type = ?") |
267 | if err != nil { | 267 | if err != nil { |
268 | return | 268 | return |
269 | } | 269 | } |
270 | } | 270 | } |
271 | for _, k := range forUpdate { | 271 | for _, k := range forUpdate { |
272 | _, err = uStmt.Exec(string(updateQue[k]), k) | 272 | _, err = uStmt.Exec(string(updateQue[k]), k) |
273 | if err != nil { | 273 | if err != nil { |
274 | logger.Log("webutility: %v\n", err) | 274 | logger.Log("webutility: %v\n", err) |
275 | return | 275 | return |
276 | } | 276 | } |
277 | upd++ | 277 | upd++ |
278 | } | 278 | } |
279 | 279 | ||
280 | blankPayload, _ := json.Marshal(Payload{}) | 280 | blankPayload, _ := json.Marshal(Payload{}) |
281 | var iStmt *sql.Stmt | 281 | var iStmt *sql.Stmt |
282 | if driver == "ora" { | 282 | if driver == "ora" { |
283 | iStmt, err = metadataDB.Prepare("INSERT INTO ENTITIES(PROJEKAT, METADATA, ENTITY_TYPE, ENTITY_MODEL) VALUES(:1, :2, :3, :4)") | 283 | iStmt, err = metadataDB.Prepare("INSERT INTO ENTITIES(PROJEKAT, METADATA, ENTITY_TYPE, ENTITY_MODEL) VALUES(:1, :2, :3, :4)") |
284 | if err != nil { | 284 | if err != nil { |
285 | return | 285 | return |
286 | } | 286 | } |
287 | } else if driver == "mysql" { | 287 | } else if driver == "mysql" { |
288 | iStmt, err = metadataDB.Prepare("INSERT INTO ENTITIES(PROJEKAT, METADATA, ENTITY_TYPE, ENTITY_MODEL) VALUES(?, ?, ?, ?)") | 288 | iStmt, err = metadataDB.Prepare("INSERT INTO ENTITIES(PROJEKAT, METADATA, ENTITY_TYPE, ENTITY_MODEL) VALUES(?, ?, ?, ?)") |
289 | if err != nil { | 289 | if err != nil { |
290 | return | 290 | return |
291 | } | 291 | } |
292 | } | 292 | } |
293 | for _, k := range forAdd { | 293 | for _, k := range forAdd { |
294 | _, err = iStmt.Exec(activeProject, string(blankPayload), k, string(updateQue[k])) | 294 | _, err = iStmt.Exec(activeProject, string(blankPayload), k, string(updateQue[k])) |
295 | if err != nil { | 295 | if err != nil { |
296 | logger.Log("webutility: %v\n", err) | 296 | logger.Log("webutility: %v\n", err) |
297 | return | 297 | return |
298 | } | 298 | } |
299 | metadata[k] = Payload{} | 299 | metadata[k] = Payload{} |
300 | add++ | 300 | add++ |
301 | } | 301 | } |
302 | 302 | ||
303 | return total, upd, add, nil | 303 | return total, upd, add, nil |
304 | } | 304 | } |
305 | 305 | ||
306 | /* | 306 | /* |
307 | func ModifyMetadataForEntity(entityType string, p *Payload) error { | 307 | func ModifyMetadataForEntity(entityType string, p *Payload) error { |
308 | md, err := json.Marshal(*p) | 308 | md, err := json.Marshal(*p) |
309 | if err != nil { | 309 | if err != nil { |
310 | return err | 310 | return err |
311 | } | 311 | } |
312 | 312 | ||
313 | mu.Lock() | 313 | mu.Lock() |
314 | defer mu.Unlock() | 314 | defer mu.Unlock() |
315 | _, err = metadataDB.PrepAndExe(`update entities set | 315 | _, err = metadataDB.PrepAndExe(`update entities set |
316 | metadata = :1 | 316 | metadata = :1 |
317 | where projekat = :2 | 317 | where projekat = :2 |
318 | and entity_type = :3`, | 318 | and entity_type = :3`, |
319 | string(md), | 319 | string(md), |
320 | activeProject, | 320 | activeProject, |
321 | entityType) | 321 | entityType) |
322 | if err != nil { | 322 | if err != nil { |
323 | return err | 323 | return err |
324 | } | 324 | } |
325 | return nil | 325 | return nil |
326 | } | 326 | } |
327 | 327 | ||
328 | func DeleteEntityModel(entityType string) error { | 328 | func DeleteEntityModel(entityType string) error { |
329 | _, err := metadataDB.PrepAndExe("delete from entities where entity_type = :1", entityType) | 329 | _, err := metadataDB.PrepAndExe("delete from entities where entity_type = :1", entityType) |
330 | if err == nil { | 330 | if err == nil { |
331 | mu.Lock() | 331 | mu.Lock() |
332 | delete(metadata, entityType) | 332 | delete(metadata, entityType) |
333 | mu.Unlock() | 333 | mu.Unlock() |
334 | } | 334 | } |
335 | return err | 335 | return err |
336 | } | 336 | } |
337 | */ | 337 | */ |
338 | 338 |
locale_utility.go
1 | package webutility | 1 | package webutility |
2 | 2 | ||
3 | import ( | 3 | import ( |
4 | "encoding/json" | 4 | "encoding/json" |
5 | "errors" | ||
5 | "io/ioutil" | 6 | "io/ioutil" |
6 | ) | 7 | ) |
7 | 8 | ||
8 | type Dictionary struct { | 9 | type Dictionary struct { |
9 | locales map[string]map[string]string | 10 | locales map[string]map[string]string |
11 | supported []string | ||
12 | defaultLocale string | ||
10 | } | 13 | } |
11 | 14 | ||
12 | func NewDictionary() Dictionary { | 15 | func NewDictionary() Dictionary { |
13 | return Dictionary{ | 16 | return Dictionary{ |
14 | locales: map[string]map[string]string{}, | 17 | locales: map[string]map[string]string{}, |
15 | } | 18 | } |
16 | } | 19 | } |
17 | 20 | ||
18 | func (d *Dictionary) AddLocale(loc, filePath string) error { | 21 | func (d *Dictionary) AddLocale(loc, filePath string) error { |
19 | file, err := ioutil.ReadFile(filePath) | 22 | file, err := ioutil.ReadFile(filePath) |
20 | if err != nil { | 23 | if err != nil { |
21 | return err | 24 | return err |
22 | } | 25 | } |
23 | 26 | ||
24 | var data interface{} | 27 | var data interface{} |
25 | err = json.Unmarshal(file, &data) | 28 | err = json.Unmarshal(file, &data) |
26 | if err != nil { | 29 | if err != nil { |
27 | return err | 30 | return err |
28 | } | 31 | } |
29 | 32 | ||
30 | l := map[string]string{} | 33 | l := map[string]string{} |
31 | for k, v := range data.(map[string]interface{}) { | 34 | for k, v := range data.(map[string]interface{}) { |
32 | l[k] = v.(string) | 35 | l[k] = v.(string) |
33 | } | 36 | } |
34 | d.locales[loc] = l | 37 | d.locales[loc] = l |
38 | d.supported = append(d.supported, loc) | ||
35 | 39 | ||
36 | return nil | 40 | return nil |
37 | } | 41 | } |
38 | 42 | ||
39 | func (d *Dictionary) Translate(loc, key string) string { | 43 | func (d *Dictionary) Translate(loc, key string) string { |
40 | return d.locales[loc][key] | 44 | return d.locales[loc][key] |
41 | } | 45 | } |
42 | 46 | ||
43 | func (d *Dictionary) SupportsLocale(loc string) bool { | 47 | func (d *Dictionary) HasLocale(loc string) bool { |
44 | for k, _ := range d.locales { | 48 | for _, v := range d.supported { |
45 | if k == loc { | 49 | if v == loc { |
46 | return true | 50 | return true |
47 | } | 51 | } |
48 | } | 52 | } |
49 | return false | 53 | return false |
50 | } | 54 | } |
55 | |||
56 | func (d *Dictionary) SetDefaultLocale(loc string) error { | ||
57 | if !d.HasLocale(loc) { | ||
58 | return errors.New("dictionary does not contain translations for " + loc) | ||
59 | } | ||
60 | d.defaultLocale = loc | ||
61 | return nil | ||
62 | } | ||
63 | |||
64 | func (d *Dictionary) GetDefaultLocale() string { | ||
65 | return d.defaultLocale | ||
66 | } | ||
51 | 67 |