Commit 368c7f87bff1233ad437437804a91bd03833238a
1 parent
fbf92700f4
Exists in
master
pagination work
Showing
2 changed files
with
25 additions
and
12 deletions
Show diff stats
auth.go
1 | package webutility | 1 | package webutility |
2 | 2 | ||
3 | import ( | 3 | import ( |
4 | "crypto/rand" | 4 | "crypto/rand" |
5 | "crypto/sha256" | 5 | "crypto/sha256" |
6 | "encoding/hex" | 6 | "encoding/hex" |
7 | "errors" | 7 | "errors" |
8 | "net/http" | 8 | "net/http" |
9 | "strings" | 9 | "strings" |
10 | "time" | 10 | "time" |
11 | 11 | ||
12 | "github.com/dgrijalva/jwt-go" | 12 | "github.com/dgrijalva/jwt-go" |
13 | ) | 13 | ) |
14 | 14 | ||
15 | var _issuer = "webutility" | 15 | var _issuer = "webutility" |
16 | var _secret = "webutility" | 16 | var _secret = "webutility" |
17 | 17 | ||
18 | // TokenClaims are JWT token claims. | 18 | // TokenClaims are JWT token claims. |
19 | type TokenClaims struct { | 19 | type TokenClaims struct { |
20 | // extending a struct | 20 | // extending a struct |
21 | jwt.StandardClaims | 21 | jwt.StandardClaims |
22 | 22 | ||
23 | // custom claims | 23 | // custom claims |
24 | Token string `json:"access_token"` | 24 | Token string `json:"access_token"` |
25 | TokenType string `json:"token_type"` | 25 | TokenType string `json:"token_type"` |
26 | Username string `json:"username"` | 26 | Username string `json:"username"` |
27 | RoleName string `json:"role"` | 27 | RoleName string `json:"role"` |
28 | RoleID int64 `json:"role_id"` | 28 | RoleID int64 `json:"role_id"` |
29 | ExpiresIn int64 `json:"expires_in"` | 29 | ExpiresIn int64 `json:"expires_in"` |
30 | } | 30 | } |
31 | 31 | ||
32 | func InitJWT(issuer, secret string) { | 32 | func InitJWT(issuer, secret string) { |
33 | _issuer = issuer | 33 | _issuer = issuer |
34 | _secret = secret | 34 | _secret = secret |
35 | } | 35 | } |
36 | 36 | ||
37 | // ValidateHash hashes pass and salt and returns comparison result with resultHash | 37 | // ValidateHash hashes pass and salt and returns comparison result with resultHash |
38 | func ValidateHash(pass, salt, resultHash string) (bool, error) { | 38 | func ValidateHash(pass, salt, resultHash string) (bool, error) { |
39 | hash, _, err := CreateHash(pass, salt) | 39 | hash, _, err := CreateHash(pass, salt) |
40 | if err != nil { | 40 | if err != nil { |
41 | return false, err | 41 | return false, err |
42 | } | 42 | } |
43 | res := hash == resultHash | 43 | res := hash == resultHash |
44 | return res, nil | 44 | return res, nil |
45 | } | 45 | } |
46 | 46 | ||
47 | // CreateHash hashes str using SHA256. | 47 | // CreateHash hashes str using SHA256. |
48 | // If the presalt parameter is not provided CreateHash will generate new salt string. | 48 | // If the presalt parameter is not provided CreateHash will generate new salt string. |
49 | // Returns hash and salt strings or an error if it fails. | 49 | // Returns hash and salt strings or an error if it fails. |
50 | func CreateHash(str, presalt string) (hash, salt string, err error) { | 50 | func CreateHash(str, presalt string) (hash, salt string, err error) { |
51 | // chech if message is presalted | 51 | // chech if message is presalted |
52 | if presalt == "" { | 52 | if presalt == "" { |
53 | salt, err = randomSalt() | 53 | salt, err = randomSalt() |
54 | if err != nil { | 54 | if err != nil { |
55 | return "", "", err | 55 | return "", "", err |
56 | } | 56 | } |
57 | } else { | 57 | } else { |
58 | salt = presalt | 58 | salt = presalt |
59 | } | 59 | } |
60 | 60 | ||
61 | // convert strings to raw byte slices | 61 | // convert strings to raw byte slices |
62 | rawstr := []byte(str) | 62 | rawstr := []byte(str) |
63 | rawsalt, err := hex.DecodeString(salt) | 63 | rawsalt, err := hex.DecodeString(salt) |
64 | if err != nil { | 64 | if err != nil { |
65 | return "", "", err | 65 | return "", "", err |
66 | } | 66 | } |
67 | 67 | ||
68 | rawdata := make([]byte, len(rawstr)+len(rawsalt)) | 68 | rawdata := make([]byte, len(rawstr)+len(rawsalt)) |
69 | rawdata = append(rawdata, rawstr...) | 69 | rawdata = append(rawdata, rawstr...) |
70 | rawdata = append(rawdata, rawsalt...) | 70 | rawdata = append(rawdata, rawsalt...) |
71 | 71 | ||
72 | // hash message + salt | 72 | // hash message + salt |
73 | hasher := sha256.New() | 73 | hasher := sha256.New() |
74 | hasher.Write(rawdata) | 74 | hasher.Write(rawdata) |
75 | rawhash := hasher.Sum(nil) | 75 | rawhash := hasher.Sum(nil) |
76 | 76 | ||
77 | hash = hex.EncodeToString(rawhash) | 77 | hash = hex.EncodeToString(rawhash) |
78 | return hash, salt, nil | 78 | return hash, salt, nil |
79 | } | 79 | } |
80 | 80 | ||
81 | // CreateAuthToken returns JWT token with encoded username, role, expiration date and issuer claims. | 81 | // CreateAuthToken returns JWT token with encoded username, role, expiration date and issuer claims. |
82 | // It returns an error if it fails. | 82 | // It returns an error if it fails. |
83 | func CreateAuthToken(username string, roleName string, roleID int64) (TokenClaims, error) { | 83 | func CreateAuthToken(username string, roleName string, roleID int64) (TokenClaims, error) { |
84 | t0 := (time.Now()).Unix() | 84 | t0 := (time.Now()).Unix() |
85 | t1 := (time.Now().Add(time.Hour * 24 * 7)).Unix() | 85 | t1 := (time.Now().Add(time.Hour * 24 * 7)).Unix() |
86 | claims := TokenClaims{ | 86 | claims := TokenClaims{ |
87 | TokenType: "Bearer", | 87 | TokenType: "Bearer", |
88 | Username: username, | 88 | Username: username, |
89 | RoleName: roleName, | 89 | RoleName: roleName, |
90 | RoleID: roleID, | 90 | RoleID: roleID, |
91 | ExpiresIn: t1 - t0, | 91 | ExpiresIn: t1 - t0, |
92 | } | 92 | } |
93 | // initialize jwt.StandardClaims fields (anonymous struct) | 93 | // initialize jwt.StandardClaims fields (anonymous struct) |
94 | claims.IssuedAt = t0 | 94 | claims.IssuedAt = t0 |
95 | claims.ExpiresAt = t1 | 95 | claims.ExpiresAt = t1 |
96 | claims.Issuer = _issuer | 96 | claims.Issuer = _issuer |
97 | 97 | ||
98 | jwtToken := jwt.NewWithClaims(jwt.SigningMethodHS256, claims) | 98 | jwtToken := jwt.NewWithClaims(jwt.SigningMethodHS256, claims) |
99 | token, err := jwtToken.SignedString([]byte(_secret)) | 99 | token, err := jwtToken.SignedString([]byte(_secret)) |
100 | if err != nil { | 100 | if err != nil { |
101 | return TokenClaims{}, err | 101 | return TokenClaims{}, err |
102 | } | 102 | } |
103 | claims.Token = token | 103 | claims.Token = token |
104 | return claims, nil | 104 | return claims, nil |
105 | } | 105 | } |
106 | 106 | ||
107 | // RefreshAuthToken returns new JWT token with same claims contained in tok but with prolonged expiration date. | 107 | // RefreshAuthToken returns new JWT token with same claims contained in tok but with prolonged expiration date. |
108 | // It returns an error if it fails. | 108 | // It returns an error if it fails. |
109 | func RefreshAuthToken(tok string) (TokenClaims, error) { | 109 | func RefreshAuthToken(tok string) (TokenClaims, error) { |
110 | token, err := jwt.ParseWithClaims(tok, &TokenClaims{}, secretFunc) | 110 | token, err := jwt.ParseWithClaims(tok, &TokenClaims{}, secretFunc) |
111 | if err != nil { | 111 | if err != nil { |
112 | if validation, ok := err.(*jwt.ValidationError); ok { | 112 | if validation, ok := err.(*jwt.ValidationError); ok { |
113 | // don't return error if token is expired | 113 | // don't return error if token is expired |
114 | // just extend it | 114 | // just extend it |
115 | if !(validation.Errors&jwt.ValidationErrorExpired != 0) { | 115 | if !(validation.Errors&jwt.ValidationErrorExpired != 0) { |
116 | return TokenClaims{}, err | 116 | return TokenClaims{}, err |
117 | } | 117 | } |
118 | } else { | 118 | } else { |
119 | return TokenClaims{}, err | 119 | return TokenClaims{}, err |
120 | } | 120 | } |
121 | } | 121 | } |
122 | 122 | ||
123 | // type assertion | 123 | // type assertion |
124 | claims, ok := token.Claims.(*TokenClaims) | 124 | claims, ok := token.Claims.(*TokenClaims) |
125 | if !ok { | 125 | if !ok { |
126 | return TokenClaims{}, errors.New("token is not valid") | 126 | return TokenClaims{}, errors.New("token is not valid") |
127 | } | 127 | } |
128 | 128 | ||
129 | // extend token expiration date | 129 | // extend token expiration date |
130 | return CreateAuthToken(claims.Username, claims.RoleName, claims.RoleID) | 130 | return CreateAuthToken(claims.Username, claims.RoleName, claims.RoleID) |
131 | } | 131 | } |
132 | 132 | ||
133 | func AuthCheck(req *http.Request, roles string) (*TokenClaims, error) { | 133 | func AuthCheck(req *http.Request, roles string) (*TokenClaims, error) { |
134 | // validate token and check expiration date | 134 | // validate token and check expiration date |
135 | claims, err := GetTokenClaims(req) | 135 | claims, err := GetTokenClaims(req) |
136 | if err != nil { | 136 | if err != nil { |
137 | return claims, err | 137 | return claims, err |
138 | } | 138 | } |
139 | 139 | ||
140 | if roles == "" { | 140 | if roles == "" { |
141 | return claims, nil | 141 | return claims, nil |
142 | } | 142 | } |
143 | 143 | ||
144 | // check if token has expired | 144 | // check if token has expired |
145 | if claims.ExpiresAt < (time.Now()).Unix() { | 145 | if claims.ExpiresAt < (time.Now()).Unix() { |
146 | return claims, errors.New("token has expired") | 146 | return claims, errors.New("token has expired") |
147 | } | 147 | } |
148 | 148 | ||
149 | if roles == "*" { | 149 | if roles == "*" { |
150 | return claims, nil | 150 | return claims, nil |
151 | } | 151 | } |
152 | 152 | ||
153 | parts := strings.Split(roles, ",") | 153 | parts := strings.Split(roles, ",") |
154 | for i, _ := range parts { | 154 | for i, _ := range parts { |
155 | r := strings.Trim(parts[i], " ") | 155 | r := strings.Trim(parts[i], " ") |
156 | if claims.RoleName == r { | 156 | if claims.RoleName == r { |
157 | return claims, nil | 157 | return claims, nil |
158 | } | 158 | } |
159 | } | 159 | } |
160 | 160 | ||
161 | return claims, nil | 161 | return claims, nil |
162 | } | 162 | } |
163 | 163 | ||
164 | // GetTokenClaims extracts JWT claims from Authorization header of req. | 164 | // GetTokenClaims extracts JWT claims from Authorization header of req. |
165 | // Returns token claims or an error. | 165 | // Returns token claims or an error. |
166 | func GetTokenClaims(req *http.Request) (*TokenClaims, error) { | 166 | func GetTokenClaims(req *http.Request) (*TokenClaims, error) { |
167 | // check for and strip 'Bearer' prefix | 167 | // check for and strip 'Bearer' prefix |
168 | var tokstr string | 168 | var tokstr string |
169 | authHead := req.Header.Get("Authorization") | 169 | authHead := req.Header.Get("Authorization") |
170 | if ok := strings.HasPrefix(authHead, "Bearer "); ok { | 170 | if ok := strings.HasPrefix(authHead, "Bearer "); ok { |
171 | tokstr = strings.TrimPrefix(authHead, "Bearer ") | 171 | tokstr = strings.TrimPrefix(authHead, "Bearer ") |
172 | } else { | 172 | } else { |
173 | return &TokenClaims{}, errors.New("authorization header in incomplete") | 173 | return &TokenClaims{}, errors.New("authorization header in incomplete") |
174 | } | 174 | } |
175 | 175 | ||
176 | token, err := jwt.ParseWithClaims(tokstr, &TokenClaims{}, secretFunc) | 176 | token, err := jwt.ParseWithClaims(tokstr, &TokenClaims{}, secretFunc) |
177 | if err != nil { | 177 | if err != nil { |
178 | return &TokenClaims{}, err | 178 | return &TokenClaims{}, err |
179 | } | 179 | } |
180 | 180 | ||
181 | // type assertion | 181 | // type assertion |
182 | claims, ok := token.Claims.(*TokenClaims) | 182 | claims, ok := token.Claims.(*TokenClaims) |
183 | if !ok || !token.Valid { | 183 | if !ok || !token.Valid { |
184 | return &TokenClaims{}, errors.New("token is not valid") | 184 | return &TokenClaims{}, errors.New("token is not valid") |
185 | } | 185 | } |
186 | 186 | ||
187 | return claims, nil | 187 | return claims, nil |
188 | } | 188 | } |
189 | 189 | ||
190 | // randomSalt returns a string of 32 random characters. | 190 | // randomSalt returns a string of 32 random characters. |
191 | const saltSize = 32 | ||
192 | |||
193 | func randomSalt() (s string, err error) { | 191 | func randomSalt() (s string, err error) { |
192 | const saltSize = 32 | ||
193 | |||
194 | rawsalt := make([]byte, saltSize) | 194 | rawsalt := make([]byte, saltSize) |
195 | 195 | ||
196 | _, err = rand.Read(rawsalt) | 196 | _, err = rand.Read(rawsalt) |
197 | if err != nil { | 197 | if err != nil { |
198 | return "", err | 198 | return "", err |
199 | } | 199 | } |
200 | 200 | ||
201 | s = hex.EncodeToString(rawsalt) | 201 | s = hex.EncodeToString(rawsalt) |
202 | return s, nil | 202 | return s, nil |
203 | } | 203 | } |
204 | 204 | ||
205 | // secretFunc returns byte slice of API secret keyword. | 205 | // secretFunc returns byte slice of API secret keyword. |
206 | func secretFunc(token *jwt.Token) (interface{}, error) { | 206 | func secretFunc(token *jwt.Token) (interface{}, error) { |
207 | return []byte(_secret), nil | 207 | return []byte(_secret), nil |
payload.go
1 | package webutility | 1 | package webutility |
2 | 2 | ||
3 | import ( | 3 | import ( |
4 | "database/sql" | 4 | "database/sql" |
5 | "encoding/json" | 5 | "encoding/json" |
6 | "errors" | 6 | "errors" |
7 | "fmt" | 7 | "fmt" |
8 | "io" | 8 | "io" |
9 | "net/http" | 9 | "net/http" |
10 | "sync" | 10 | "sync" |
11 | "time" | 11 | "time" |
12 | 12 | ||
13 | "git.to-net.rs/marko.tikvic/gologger" | 13 | "git.to-net.rs/marko.tikvic/gologger" |
14 | ) | 14 | ) |
15 | 15 | ||
16 | var ( | 16 | var ( |
17 | mu = &sync.Mutex{} | 17 | mu = &sync.Mutex{} |
18 | metadata = make(map[string]Payload) | 18 | metadata = make(map[string]Payload) |
19 | 19 | ||
20 | updateQue = make(map[string][]byte) | 20 | updateQue = make(map[string][]byte) |
21 | 21 | ||
22 | metadataDB *sql.DB | 22 | metadataDB *sql.DB |
23 | activeProject string | 23 | activeProject string |
24 | 24 | ||
25 | inited bool | 25 | inited bool |
26 | driver string | 26 | driver string |
27 | logger *gologger.Logger | 27 | logger *gologger.Logger |
28 | ) | 28 | ) |
29 | 29 | ||
30 | type LangMap map[string]map[string]string | 30 | type LangMap map[string]map[string]string |
31 | 31 | ||
32 | type Field struct { | 32 | type Field struct { |
33 | Parameter string `json:"param"` | 33 | Parameter string `json:"param"` |
34 | Type string `json:"type"` | 34 | Type string `json:"type"` |
35 | Visible bool `json:"visible"` | 35 | Visible bool `json:"visible"` |
36 | Editable bool `json:"editable"` | 36 | Editable bool `json:"editable"` |
37 | } | 37 | } |
38 | 38 | ||
39 | type CorrelationField struct { | 39 | type CorrelationField struct { |
40 | Result string `json:"result"` | 40 | Result string `json:"result"` |
41 | Elements []string `json:"elements"` | 41 | Elements []string `json:"elements"` |
42 | Type string `json:"type"` | 42 | Type string `json:"type"` |
43 | } | 43 | } |
44 | 44 | ||
45 | type Translation struct { | 45 | type Translation struct { |
46 | Language string `json:"language"` | 46 | Language string `json:"language"` |
47 | FieldsLabels map[string]string `json:"fieldsLabels"` | 47 | FieldsLabels map[string]string `json:"fieldsLabels"` |
48 | } | 48 | } |
49 | 49 | ||
50 | // output | ||
50 | type PaginationLinks struct { | 51 | type PaginationLinks struct { |
51 | Base string `json:"base"` | 52 | Base string `json:"base"` |
52 | Next string `json:"next"` | 53 | Next string `json:"next"` |
53 | Prev string `json:"prev"` | 54 | Prev string `json:"prev"` |
54 | Self string `json:"self"` | 55 | Self string `json:"self"` |
55 | } | 56 | } |
56 | 57 | ||
58 | // input | ||
57 | type PaginationParameters struct { | 59 | type PaginationParameters struct { |
60 | URL string `json:"-"` | ||
58 | Offset int64 `json:"offset"` | 61 | Offset int64 `json:"offset"` |
59 | Limit int64 `json:"limit"` | 62 | Limit int64 `json:"limit"` |
60 | SortBy string `json:"sortBy"` | 63 | SortBy string `json:"sortBy"` |
61 | Order string `json:"order"` | 64 | Order string `json:"order"` |
62 | } | 65 | } |
63 | 66 | ||
67 | // TODO(marko) | ||
68 | func GetPaginationParameters(req *http.Request) (p PaginationParameters) { | ||
69 | return p | ||
70 | } | ||
71 | |||
72 | // TODO(marko) | ||
73 | func (p *PaginationParameters) paginationLinks() (links PaginationLinks) { | ||
74 | return links | ||
75 | } | ||
76 | |||
64 | type Payload struct { | 77 | type Payload struct { |
65 | Method string `json:"method"` | 78 | Method string `json:"method"` |
66 | Params map[string]string `json:"params"` | 79 | Params map[string]string `json:"params"` |
67 | Lang []Translation `json:"lang"` | 80 | Lang []Translation `json:"lang"` |
68 | Fields []Field `json:"fields"` | 81 | Fields []Field `json:"fields"` |
69 | Correlations []CorrelationField `json:"correlationFields"` | 82 | Correlations []CorrelationField `json:"correlationFields"` |
70 | IdField string `json:"idField"` | 83 | IdField string `json:"idField"` |
71 | 84 | ||
72 | // Pagination | 85 | // Pagination |
73 | Count int64 `json:"count"` | 86 | Count int64 `json:"count"` |
74 | Total int64 `json:"total"` | 87 | Total int64 `json:"total"` |
75 | Links *PaginationLinks `json:"_links"` | 88 | Links PaginationLinks `json:"_links"` |
76 | 89 | ||
77 | // Data holds JSON payload. It can't be used for itteration. | 90 | // Data holds JSON payload. It can't be used for itteration. |
78 | Data interface{} `json:"data"` | 91 | Data interface{} `json:"data"` |
79 | } | 92 | } |
80 | 93 | ||
81 | func (p *Payload) SetData(data interface{}) { | 94 | func (p *Payload) SetData(data interface{}) { |
82 | p.Data = data | 95 | p.Data = data |
83 | } | 96 | } |
84 | 97 | ||
85 | func (p *Payload) SetPaginationInfo(reqUrl string, count, total int64, params PaginationParameters) { | 98 | func (p *Payload) SetPaginationInfo(count, total int64, params PaginationParameters) { |
86 | p.Count = count | 99 | p.Count = count |
87 | p.Total = total | 100 | p.Total = total |
88 | 101 | p.Links = params.paginationLinks() | |
89 | } | 102 | } |
90 | 103 | ||
91 | // NewPayload returs a payload sceleton for entity described with etype. | 104 | // NewPayload returs a payload sceleton for entity described with key. |
92 | func NewPayload(r *http.Request, etype string) Payload { | 105 | func NewPayload(r *http.Request, key string) Payload { |
93 | pload := metadata[etype] | 106 | p := metadata[key] |
94 | pload.Method = r.Method + " " + r.RequestURI | 107 | p.Method = r.Method + " " + r.RequestURI |
95 | return pload | 108 | return p |
96 | } | 109 | } |
97 | 110 | ||
98 | // DecodeJSON decodes JSON data from r to v. | 111 | // DecodeJSON decodes JSON data from r to v. |
99 | // Returns an error if it fails. | 112 | // Returns an error if it fails. |
100 | func DecodeJSON(r io.Reader, v interface{}) error { | 113 | func DecodeJSON(r io.Reader, v interface{}) error { |
101 | return json.NewDecoder(r).Decode(v) | 114 | return json.NewDecoder(r).Decode(v) |
102 | } | 115 | } |
103 | 116 | ||
104 | // InitPayloadsMetadata loads all payloads' information into 'metadata' variable. | 117 | // InitPayloadsMetadata loads all payloads' information into 'metadata' variable. |
105 | func InitPayloadsMetadata(drv string, db *sql.DB, project string) error { | 118 | func InitPayloadsMetadata(drv string, db *sql.DB, project string) error { |
106 | var err error | 119 | var err error |
107 | if drv != "ora" && drv != "mysql" { | 120 | if drv != "ora" && drv != "mysql" { |
108 | err = errors.New("driver not supported") | 121 | err = errors.New("driver not supported") |
109 | return err | 122 | return err |
110 | } | 123 | } |
111 | 124 | ||
112 | driver = drv | 125 | driver = drv |
113 | metadataDB = db | 126 | metadataDB = db |
114 | activeProject = project | 127 | activeProject = project |
115 | 128 | ||
116 | logger, err = gologger.New("metadata", gologger.MaxLogSize100KB) | 129 | logger, err = gologger.New("metadata", gologger.MaxLogSize100KB) |
117 | if err != nil { | 130 | if err != nil { |
118 | fmt.Printf("webutility: %s\n", err.Error()) | 131 | fmt.Printf("webutility: %s\n", err.Error()) |
119 | } | 132 | } |
120 | 133 | ||
121 | mu.Lock() | 134 | mu.Lock() |
122 | defer mu.Unlock() | 135 | defer mu.Unlock() |
123 | err = initMetadata(project) | 136 | err = initMetadata(project) |
124 | if err != nil { | 137 | if err != nil { |
125 | return err | 138 | return err |
126 | } | 139 | } |
127 | inited = true | 140 | inited = true |
128 | 141 | ||
129 | return nil | 142 | return nil |
130 | } | 143 | } |
131 | 144 | ||
132 | func EnableHotloading(interval int) { | 145 | func EnableHotloading(interval int) { |
133 | if interval > 0 { | 146 | if interval > 0 { |
134 | go hotload(interval) | 147 | go hotload(interval) |
135 | } | 148 | } |
136 | } | 149 | } |
137 | 150 | ||
138 | func GetMetadataForAllEntities() map[string]Payload { | 151 | func GetMetadataForAllEntities() map[string]Payload { |
139 | return metadata | 152 | return metadata |
140 | } | 153 | } |
141 | 154 | ||
142 | func GetMetadataForEntity(t string) (Payload, bool) { | 155 | func GetMetadataForEntity(t string) (Payload, bool) { |
143 | p, ok := metadata[t] | 156 | p, ok := metadata[t] |
144 | return p, ok | 157 | return p, ok |
145 | } | 158 | } |
146 | 159 | ||
147 | func QueEntityModelUpdate(entityType string, v interface{}) { | 160 | func QueEntityModelUpdate(entityType string, v interface{}) { |
148 | updateQue[entityType], _ = json.Marshal(v) | 161 | updateQue[entityType], _ = json.Marshal(v) |
149 | } | 162 | } |
150 | 163 | ||
151 | func UpdateEntityModels(command string) (total, upd, add int, err error) { | 164 | func UpdateEntityModels(command string) (total, upd, add int, err error) { |
152 | if command != "force" && command != "missing" { | 165 | if command != "force" && command != "missing" { |
153 | return total, 0, 0, errors.New("webutility: unknown command: " + command) | 166 | return total, 0, 0, errors.New("webutility: unknown command: " + command) |
154 | } | 167 | } |
155 | 168 | ||
156 | if !inited { | 169 | if !inited { |
157 | return 0, 0, 0, errors.New("webutility: metadata not initialized but update was tried.") | 170 | return 0, 0, 0, errors.New("webutility: metadata not initialized but update was tried.") |
158 | } | 171 | } |
159 | 172 | ||
160 | total = len(updateQue) | 173 | total = len(updateQue) |
161 | 174 | ||
162 | toUpdate := make([]string, 0) | 175 | toUpdate := make([]string, 0) |
163 | toAdd := make([]string, 0) | 176 | toAdd := make([]string, 0) |
164 | 177 | ||
165 | for k, _ := range updateQue { | 178 | for k, _ := range updateQue { |
166 | if _, exists := metadata[k]; exists { | 179 | if _, exists := metadata[k]; exists { |
167 | if command == "force" { | 180 | if command == "force" { |
168 | toUpdate = append(toUpdate, k) | 181 | toUpdate = append(toUpdate, k) |
169 | } | 182 | } |
170 | } else { | 183 | } else { |
171 | toAdd = append(toAdd, k) | 184 | toAdd = append(toAdd, k) |
172 | } | 185 | } |
173 | } | 186 | } |
174 | 187 | ||
175 | var uStmt *sql.Stmt | 188 | var uStmt *sql.Stmt |
176 | if driver == "ora" { | 189 | if driver == "ora" { |
177 | uStmt, err = metadataDB.Prepare("update entities set entity_model = :1 where entity_type = :2") | 190 | uStmt, err = metadataDB.Prepare("update entities set entity_model = :1 where entity_type = :2") |
178 | if err != nil { | 191 | if err != nil { |
179 | logger.Trace(err.Error()) | 192 | logger.Trace(err.Error()) |
180 | return | 193 | return |
181 | } | 194 | } |
182 | } else if driver == "mysql" { | 195 | } else if driver == "mysql" { |
183 | uStmt, err = metadataDB.Prepare("update entities set entity_model = ? where entity_type = ?") | 196 | uStmt, err = metadataDB.Prepare("update entities set entity_model = ? where entity_type = ?") |
184 | if err != nil { | 197 | if err != nil { |
185 | logger.Trace(err.Error()) | 198 | logger.Trace(err.Error()) |
186 | return | 199 | return |
187 | } | 200 | } |
188 | } | 201 | } |
189 | for _, k := range toUpdate { | 202 | for _, k := range toUpdate { |
190 | _, err = uStmt.Exec(string(updateQue[k]), k) | 203 | _, err = uStmt.Exec(string(updateQue[k]), k) |
191 | if err != nil { | 204 | if err != nil { |
192 | logger.Trace(err.Error()) | 205 | logger.Trace(err.Error()) |
193 | return | 206 | return |
194 | } | 207 | } |
195 | upd++ | 208 | upd++ |
196 | } | 209 | } |
197 | 210 | ||
198 | blankPayload, _ := json.Marshal(Payload{}) | 211 | blankPayload, _ := json.Marshal(Payload{}) |
199 | var iStmt *sql.Stmt | 212 | var iStmt *sql.Stmt |
200 | if driver == "ora" { | 213 | if driver == "ora" { |
201 | iStmt, err = metadataDB.Prepare("insert into entities(projekat, metadata, entity_type, entity_model) values(:1, :2, :3, :4)") | 214 | iStmt, err = metadataDB.Prepare("insert into entities(projekat, metadata, entity_type, entity_model) values(:1, :2, :3, :4)") |
202 | if err != nil { | 215 | if err != nil { |
203 | logger.Trace(err.Error()) | 216 | logger.Trace(err.Error()) |
204 | return | 217 | return |
205 | } | 218 | } |
206 | } else if driver == "mysql" { | 219 | } else if driver == "mysql" { |
207 | iStmt, err = metadataDB.Prepare("insert into entities(projekat, metadata, entity_type, entity_model) values(?, ?, ?, ?)") | 220 | iStmt, err = metadataDB.Prepare("insert into entities(projekat, metadata, entity_type, entity_model) values(?, ?, ?, ?)") |
208 | if err != nil { | 221 | if err != nil { |
209 | logger.Trace(err.Error()) | 222 | logger.Trace(err.Error()) |
210 | return | 223 | return |
211 | } | 224 | } |
212 | } | 225 | } |
213 | for _, k := range toAdd { | 226 | for _, k := range toAdd { |
214 | _, err = iStmt.Exec(activeProject, string(blankPayload), k, string(updateQue[k])) | 227 | _, err = iStmt.Exec(activeProject, string(blankPayload), k, string(updateQue[k])) |
215 | if err != nil { | 228 | if err != nil { |
216 | logger.Trace(err.Error()) | 229 | logger.Trace(err.Error()) |
217 | return | 230 | return |
218 | } | 231 | } |
219 | metadata[k] = Payload{} | 232 | metadata[k] = Payload{} |
220 | add++ | 233 | add++ |
221 | } | 234 | } |
222 | 235 | ||
223 | return total, upd, add, nil | 236 | return total, upd, add, nil |
224 | } | 237 | } |
225 | 238 | ||
226 | func initMetadata(project string) error { | 239 | func initMetadata(project string) error { |
227 | rows, err := metadataDB.Query(`select | 240 | rows, err := metadataDB.Query(`select |
228 | entity_type, | 241 | entity_type, |
229 | metadata | 242 | metadata |
230 | from entities | 243 | from entities |
231 | where projekat = ` + fmt.Sprintf("'%s'", project)) | 244 | where projekat = ` + fmt.Sprintf("'%s'", project)) |
232 | if err != nil { | 245 | if err != nil { |
233 | return err | 246 | return err |
234 | } | 247 | } |
235 | defer rows.Close() | 248 | defer rows.Close() |
236 | 249 | ||
237 | if len(metadata) > 0 { | 250 | if len(metadata) > 0 { |
238 | metadata = nil | 251 | metadata = nil |
239 | } | 252 | } |
240 | metadata = make(map[string]Payload) | 253 | metadata = make(map[string]Payload) |
241 | for rows.Next() { | 254 | for rows.Next() { |
242 | var name, load string | 255 | var name, load string |
243 | rows.Scan(&name, &load) | 256 | rows.Scan(&name, &load) |
244 | 257 | ||
245 | p := Payload{} | 258 | p := Payload{} |
246 | err := json.Unmarshal([]byte(load), &p) | 259 | err := json.Unmarshal([]byte(load), &p) |
247 | if err != nil { | 260 | if err != nil { |
248 | logger.Log("webutility: couldn't init: '%s' metadata: %s:\n%s\n", name, err.Error(), load) | 261 | logger.Log("webutility: couldn't init: '%s' metadata: %s:\n%s\n", name, err.Error(), load) |
249 | } else { | 262 | } else { |
250 | metadata[name] = p | 263 | metadata[name] = p |
251 | } | 264 | } |
252 | } | 265 | } |
253 | 266 | ||
254 | return nil | 267 | return nil |
255 | } | 268 | } |
256 | 269 | ||
257 | func hotload(n int) { | 270 | func hotload(n int) { |
258 | entityScan := make(map[string]int64) | 271 | entityScan := make(map[string]int64) |
259 | firstCheck := true | 272 | firstCheck := true |
260 | for { | 273 | for { |
261 | time.Sleep(time.Duration(n) * time.Second) | 274 | time.Sleep(time.Duration(n) * time.Second) |
262 | rows, err := metadataDB.Query(`select | 275 | rows, err := metadataDB.Query(`select |
263 | ora_rowscn, | 276 | ora_rowscn, |
264 | entity_type | 277 | entity_type |
265 | from entities where projekat = ` + fmt.Sprintf("'%s'", activeProject)) | 278 | from entities where projekat = ` + fmt.Sprintf("'%s'", activeProject)) |
266 | if err != nil { | 279 | if err != nil { |
267 | logger.Log("webutility: hotload failed: %v\n", err) | 280 | logger.Log("webutility: hotload failed: %v\n", err) |
268 | time.Sleep(time.Duration(n) * time.Second) | 281 | time.Sleep(time.Duration(n) * time.Second) |
269 | continue | 282 | continue |
270 | } | 283 | } |
271 | 284 | ||
272 | var toRefresh []string | 285 | var toRefresh []string |
273 | for rows.Next() { | 286 | for rows.Next() { |
274 | var scanID int64 | 287 | var scanID int64 |
275 | var entity string | 288 | var entity string |
276 | rows.Scan(&scanID, &entity) | 289 | rows.Scan(&scanID, &entity) |
277 | oldID, ok := entityScan[entity] | 290 | oldID, ok := entityScan[entity] |
278 | if !ok || oldID != scanID { | 291 | if !ok || oldID != scanID { |
279 | entityScan[entity] = scanID | 292 | entityScan[entity] = scanID |
280 | toRefresh = append(toRefresh, entity) | 293 | toRefresh = append(toRefresh, entity) |
281 | } | 294 | } |
282 | } | 295 | } |
283 | rows.Close() | 296 | rows.Close() |
284 | 297 | ||
285 | if rows.Err() != nil { | 298 | if rows.Err() != nil { |
286 | logger.Log("webutility: hotload rset error: %v\n", rows.Err()) | 299 | logger.Log("webutility: hotload rset error: %v\n", rows.Err()) |
287 | time.Sleep(time.Duration(n) * time.Second) | 300 | time.Sleep(time.Duration(n) * time.Second) |
288 | continue | 301 | continue |
289 | } | 302 | } |
290 | 303 | ||
291 | if len(toRefresh) > 0 && !firstCheck { | 304 | if len(toRefresh) > 0 && !firstCheck { |
292 | mu.Lock() | 305 | mu.Lock() |
293 | refreshMetadata(toRefresh) | 306 | refreshMetadata(toRefresh) |
294 | mu.Unlock() | 307 | mu.Unlock() |
295 | } | 308 | } |
296 | if firstCheck { | 309 | if firstCheck { |
297 | firstCheck = false | 310 | firstCheck = false |
298 | } | 311 | } |
299 | } | 312 | } |
300 | } | 313 | } |
301 | 314 | ||
302 | func refreshMetadata(entities []string) { | 315 | func refreshMetadata(entities []string) { |
303 | for _, e := range entities { | 316 | for _, e := range entities { |
304 | fmt.Printf("refreshing %s\n", e) | 317 | fmt.Printf("refreshing %s\n", e) |
305 | rows, err := metadataDB.Query(`select | 318 | rows, err := metadataDB.Query(`select |
306 | metadata | 319 | metadata |
307 | from entities | 320 | from entities |
308 | where projekat = ` + fmt.Sprintf("'%s'", activeProject) + | 321 | where projekat = ` + fmt.Sprintf("'%s'", activeProject) + |
309 | ` and entity_type = ` + fmt.Sprintf("'%s'", e)) | 322 | ` and entity_type = ` + fmt.Sprintf("'%s'", e)) |
310 | 323 | ||
311 | if err != nil { | 324 | if err != nil { |
312 | logger.Log("webutility: refresh: prep: %v\n", err) | 325 | logger.Log("webutility: refresh: prep: %v\n", err) |
313 | rows.Close() | 326 | rows.Close() |
314 | continue | 327 | continue |
315 | } | 328 | } |
316 | 329 | ||
317 | for rows.Next() { | 330 | for rows.Next() { |
318 | var load string | 331 | var load string |
319 | rows.Scan(&load) | 332 | rows.Scan(&load) |
320 | p := Payload{} | 333 | p := Payload{} |
321 | err := json.Unmarshal([]byte(load), &p) | 334 | err := json.Unmarshal([]byte(load), &p) |
322 | if err != nil { | 335 | if err != nil { |
323 | logger.Log("webutility: couldn't refresh: '%s' metadata: %s\n%s\n", e, err.Error(), load) | 336 | logger.Log("webutility: couldn't refresh: '%s' metadata: %s\n%s\n", e, err.Error(), load) |
324 | } else { | 337 | } else { |
325 | metadata[e] = p | 338 | metadata[e] = p |
326 | } | 339 | } |
327 | } | 340 | } |
328 | rows.Close() | 341 | rows.Close() |
329 | } | 342 | } |
330 | } | 343 | } |
331 | 344 | ||
332 | /* | 345 | /* |
333 | func ModifyMetadataForEntity(entityType string, p *Payload) error { | 346 | func ModifyMetadataForEntity(entityType string, p *Payload) error { |
334 | md, err := json.Marshal(*p) | 347 | md, err := json.Marshal(*p) |
335 | if err != nil { | 348 | if err != nil { |
336 | return err | 349 | return err |
337 | } | 350 | } |
338 | 351 | ||
339 | mu.Lock() | 352 | mu.Lock() |
340 | defer mu.Unlock() | 353 | defer mu.Unlock() |
341 | _, err = metadataDB.PrepAndExe(`update entities set | 354 | _, err = metadataDB.PrepAndExe(`update entities set |
342 | metadata = :1 | 355 | metadata = :1 |
343 | where projekat = :2 | 356 | where projekat = :2 |
344 | and entity_type = :3`, | 357 | and entity_type = :3`, |
345 | string(md), | 358 | string(md), |
346 | activeProject, | 359 | activeProject, |
347 | entityType) | 360 | entityType) |
348 | if err != nil { | 361 | if err != nil { |
349 | return err | 362 | return err |
350 | } | 363 | } |
351 | return nil | 364 | return nil |
352 | } | 365 | } |
353 | 366 | ||
354 | func DeleteEntityModel(entityType string) error { | 367 | func DeleteEntityModel(entityType string) error { |
355 | _, err := metadataDB.PrepAndExe("delete from entities where entity_type = :1", entityType) | 368 | _, err := metadataDB.PrepAndExe("delete from entities where entity_type = :1", entityType) |
356 | if err == nil { | 369 | if err == nil { |
357 | mu.Lock() | 370 | mu.Lock() |
358 | delete(metadata, entityType) | 371 | delete(metadata, entityType) |
359 | mu.Unlock() | 372 | mu.Unlock() |
360 | } | 373 | } |
361 | return err | 374 | return err |
362 | } | 375 | } |
363 | */ | 376 | */ |
364 | 377 |