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