Commit 63b2ae620176e76f604e31887ed0819b0b29e4da
1 parent
087f8fb21c
Exists in
master
and in
1 other branch
renamed files
Showing
10 changed files
with
866 additions
and
866 deletions
Show diff stats
auth.go
... | ... | @@ -0,0 +1,246 @@ |
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 | +} | ... | ... |
auth_utility.go
... | ... | @@ -1,246 +0,0 @@ |
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 | -// 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 | -} |
format.go
... | ... | @@ -0,0 +1,50 @@ |
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 | +} | ... | ... |
format_utility.go
... | ... | @@ -1,50 +0,0 @@ |
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 | -} |
http.go
... | ... | @@ -0,0 +1,165 @@ |
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 | +} | ... | ... |
http_utility.go
... | ... | @@ -1,165 +0,0 @@ |
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 | -} |
json.go
... | ... | @@ -0,0 +1,339 @@ |
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 | +*/ | ... | ... |
json_utility.go
... | ... | @@ -1,339 +0,0 @@ |
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 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 | -*/ |
locale_utility.go
... | ... | @@ -1,66 +0,0 @@ |
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 | -} |
localization.go
... | ... | @@ -0,0 +1,66 @@ |
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 | +} | ... | ... |