Commit 63b2ae620176e76f604e31887ed0819b0b29e4da

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

renamed files

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