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