Commit a205e8f40ddf1621558e6c29c3fed7aa2134f1a6

Authored by Marko Tikvić
1 parent f84e7607d3
Exists in master and in 1 other branch v2

changes

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