Commit 11933054acf481aff93c2216d5d233aad635b0f6
1 parent
46b2215ebd
Exists in
master
Added Get, Post and Put methods
Showing
2 changed files
with
76 additions
and
7 deletions
Show diff stats
http.go
1 | package webutility | 1 | package webutility |
2 | 2 | ||
3 | import ( | 3 | import ( |
4 | "bytes" | ||
4 | "encoding/json" | 5 | "encoding/json" |
5 | "fmt" | 6 | "fmt" |
7 | "io" | ||
6 | "net/http" | 8 | "net/http" |
9 | "net/url" | ||
7 | ) | 10 | ) |
8 | 11 | ||
9 | // StatusRecorder ... | 12 | // StatusRecorder ... |
10 | type StatusRecorder struct { | 13 | type StatusRecorder struct { |
11 | writer http.ResponseWriter | 14 | writer http.ResponseWriter |
12 | status int | 15 | status int |
13 | size int | 16 | size int |
14 | } | 17 | } |
15 | 18 | ||
16 | // NewStatusRecorder ... | 19 | // NewStatusRecorder ... |
17 | func NewStatusRecorder(w http.ResponseWriter) *StatusRecorder { | 20 | func NewStatusRecorder(w http.ResponseWriter) *StatusRecorder { |
18 | return &StatusRecorder{ | 21 | return &StatusRecorder{ |
19 | writer: w, | 22 | writer: w, |
20 | status: 0, | 23 | status: 0, |
21 | size: 0, | 24 | size: 0, |
22 | } | 25 | } |
23 | } | 26 | } |
24 | 27 | ||
25 | // WriteHeader is a wrapper http.ResponseWriter interface | 28 | // WriteHeader is a wrapper http.ResponseWriter interface |
26 | func (r *StatusRecorder) WriteHeader(code int) { | 29 | func (r *StatusRecorder) WriteHeader(code int) { |
27 | r.status = code | 30 | r.status = code |
28 | r.writer.WriteHeader(code) | 31 | r.writer.WriteHeader(code) |
29 | } | 32 | } |
30 | 33 | ||
31 | // Write is a wrapper for http.ResponseWriter interface | 34 | // Write is a wrapper for http.ResponseWriter interface |
32 | func (r *StatusRecorder) Write(in []byte) (int, error) { | 35 | func (r *StatusRecorder) Write(in []byte) (int, error) { |
33 | r.size = len(in) | 36 | r.size = len(in) |
34 | return r.writer.Write(in) | 37 | return r.writer.Write(in) |
35 | } | 38 | } |
36 | 39 | ||
37 | // Header is a wrapper for http.ResponseWriter interface | 40 | // Header is a wrapper for http.ResponseWriter interface |
38 | func (r *StatusRecorder) Header() http.Header { | 41 | func (r *StatusRecorder) Header() http.Header { |
39 | return r.writer.Header() | 42 | return r.writer.Header() |
40 | } | 43 | } |
41 | 44 | ||
42 | // Status ... | 45 | // Status ... |
43 | func (r *StatusRecorder) Status() int { | 46 | func (r *StatusRecorder) Status() int { |
44 | return r.status | 47 | return r.status |
45 | } | 48 | } |
46 | 49 | ||
47 | // Size ... | 50 | // Size ... |
48 | func (r *StatusRecorder) Size() int { | 51 | func (r *StatusRecorder) Size() int { |
49 | return r.size | 52 | return r.size |
50 | } | 53 | } |
51 | 54 | ||
52 | // NotFoundHandlerFunc writes HTTP error 404 to w. | 55 | // NotFoundHandlerFunc writes HTTP error 404 to w. |
53 | func NotFoundHandlerFunc(w http.ResponseWriter, req *http.Request) { | 56 | func NotFoundHandlerFunc(w http.ResponseWriter, req *http.Request) { |
54 | SetDefaultHeaders(w) | 57 | SetDefaultHeaders(w) |
55 | if req.Method == "OPTIONS" { | 58 | if req.Method == "OPTIONS" { |
56 | return | 59 | return |
57 | } | 60 | } |
58 | NotFound(w, req, fmt.Sprintf("Resource you requested was not found: %s", req.URL.String())) | 61 | NotFound(w, req, fmt.Sprintf("Resource you requested was not found: %s", req.URL.String())) |
59 | } | 62 | } |
60 | 63 | ||
61 | // SetContentType ... | 64 | // SetContentType ... |
62 | func SetContentType(w http.ResponseWriter, ctype string) { | 65 | func SetContentType(w http.ResponseWriter, ctype string) { |
63 | w.Header().Set("Content-Type", ctype) | 66 | w.Header().Set("Content-Type", ctype) |
64 | } | 67 | } |
65 | 68 | ||
66 | // SetResponseStatus ... | 69 | // SetResponseStatus ... |
67 | func SetResponseStatus(w http.ResponseWriter, status int) { | 70 | func SetResponseStatus(w http.ResponseWriter, status int) { |
68 | w.WriteHeader(status) | 71 | w.WriteHeader(status) |
69 | } | 72 | } |
70 | 73 | ||
71 | // WriteResponse ... | 74 | // WriteResponse ... |
72 | func WriteResponse(w http.ResponseWriter, content []byte) { | 75 | func WriteResponse(w http.ResponseWriter, content []byte) { |
73 | w.Write(content) | 76 | w.Write(content) |
74 | } | 77 | } |
75 | 78 | ||
76 | // SetDefaultHeaders set's default headers for an HTTP response. | 79 | // SetDefaultHeaders set's default headers for an HTTP response. |
77 | func SetDefaultHeaders(w http.ResponseWriter) { | 80 | func SetDefaultHeaders(w http.ResponseWriter) { |
78 | w.Header().Set("Access-Control-Allow-Origin", "*") | 81 | w.Header().Set("Access-Control-Allow-Origin", "*") |
79 | w.Header().Set("Access-Control-Allow-Methods", "POST, GET, PUT, DELETE, OPTIONS") | 82 | w.Header().Set("Access-Control-Allow-Methods", "POST, GET, PUT, DELETE, OPTIONS") |
80 | w.Header().Set("Access-Control-Allow-Headers", "Accept, Content-Type, Content-Length, Accept-Encoding, X-CSRF-Token, Authorization") | 83 | w.Header().Set("Access-Control-Allow-Headers", "Accept, Content-Type, Content-Length, Accept-Encoding, X-CSRF-Token, Authorization") |
81 | SetContentType(w, "application/json; charset=utf-8") | 84 | SetContentType(w, "application/json; charset=utf-8") |
82 | } | 85 | } |
83 | 86 | ||
84 | // GetLocale ... | 87 | // GetLocale ... |
85 | func GetLocale(req *http.Request, dflt string) string { | 88 | func GetLocale(req *http.Request, dflt string) string { |
86 | loc := req.FormValue("locale") | 89 | loc := req.FormValue("locale") |
87 | if loc == "" { | 90 | if loc == "" { |
88 | return dflt | 91 | return dflt |
89 | } | 92 | } |
90 | return loc | 93 | return loc |
91 | } | 94 | } |
92 | 95 | ||
93 | // Success ... | 96 | // Success ... |
94 | func Success(w http.ResponseWriter, payload interface{}, code int) { | 97 | func Success(w http.ResponseWriter, payload interface{}, code int) { |
95 | w.WriteHeader(code) | 98 | w.WriteHeader(code) |
96 | if payload != nil { | 99 | if payload != nil { |
97 | json.NewEncoder(w).Encode(payload) | 100 | json.NewEncoder(w).Encode(payload) |
98 | } | 101 | } |
99 | } | 102 | } |
100 | 103 | ||
101 | // OK ... | 104 | // OK ... |
102 | func OK(w http.ResponseWriter, payload interface{}) { | 105 | func OK(w http.ResponseWriter, payload interface{}) { |
103 | Success(w, payload, http.StatusOK) | 106 | Success(w, payload, http.StatusOK) |
104 | } | 107 | } |
105 | 108 | ||
106 | // Created ... | 109 | // Created ... |
107 | func Created(w http.ResponseWriter, payload interface{}) { | 110 | func Created(w http.ResponseWriter, payload interface{}) { |
108 | Success(w, payload, http.StatusCreated) | 111 | Success(w, payload, http.StatusCreated) |
109 | } | 112 | } |
110 | 113 | ||
111 | type weberror struct { | 114 | type weberror struct { |
112 | Request string `json:"request"` | 115 | Request string `json:"request"` |
113 | Error string `json:"error"` | 116 | Error string `json:"error"` |
114 | } | 117 | } |
115 | 118 | ||
116 | // Error ... | 119 | // Error ... |
117 | func Error(w http.ResponseWriter, r *http.Request, code int, err string) { | 120 | func Error(w http.ResponseWriter, r *http.Request, code int, err string) { |
118 | werr := weberror{Error: err, Request: r.Method + " " + r.RequestURI} | 121 | werr := weberror{Error: err, Request: r.Method + " " + r.RequestURI} |
119 | w.WriteHeader(code) | 122 | w.WriteHeader(code) |
120 | json.NewEncoder(w).Encode(werr) | 123 | json.NewEncoder(w).Encode(werr) |
121 | } | 124 | } |
122 | 125 | ||
123 | // BadRequest ... | 126 | // BadRequest ... |
124 | func BadRequest(w http.ResponseWriter, r *http.Request, err string) { | 127 | func BadRequest(w http.ResponseWriter, r *http.Request, err string) { |
125 | Error(w, r, http.StatusBadRequest, err) | 128 | Error(w, r, http.StatusBadRequest, err) |
126 | } | 129 | } |
127 | 130 | ||
128 | // Unauthorized ... | 131 | // Unauthorized ... |
129 | func Unauthorized(w http.ResponseWriter, r *http.Request, err string) { | 132 | func Unauthorized(w http.ResponseWriter, r *http.Request, err string) { |
130 | Error(w, r, http.StatusUnauthorized, err) | 133 | Error(w, r, http.StatusUnauthorized, err) |
131 | } | 134 | } |
132 | 135 | ||
133 | // Forbidden ... | 136 | // Forbidden ... |
134 | func Forbidden(w http.ResponseWriter, r *http.Request, err string) { | 137 | func Forbidden(w http.ResponseWriter, r *http.Request, err string) { |
135 | Error(w, r, http.StatusForbidden, err) | 138 | Error(w, r, http.StatusForbidden, err) |
136 | } | 139 | } |
137 | 140 | ||
138 | // NotFound ... | 141 | // NotFound ... |
139 | func NotFound(w http.ResponseWriter, r *http.Request, err string) { | 142 | func NotFound(w http.ResponseWriter, r *http.Request, err string) { |
140 | Error(w, r, http.StatusNotFound, err) | 143 | Error(w, r, http.StatusNotFound, err) |
141 | } | 144 | } |
142 | 145 | ||
143 | // Conflict ... | 146 | // Conflict ... |
144 | func Conflict(w http.ResponseWriter, r *http.Request, err string) { | 147 | func Conflict(w http.ResponseWriter, r *http.Request, err string) { |
145 | Error(w, r, http.StatusConflict, err) | 148 | Error(w, r, http.StatusConflict, err) |
146 | } | 149 | } |
147 | 150 | ||
148 | // InternalServerError ... | 151 | // InternalServerError ... |
149 | func InternalServerError(w http.ResponseWriter, r *http.Request, err string) { | 152 | func InternalServerError(w http.ResponseWriter, r *http.Request, err string) { |
150 | Error(w, r, http.StatusInternalServerError, err) | 153 | Error(w, r, http.StatusInternalServerError, err) |
151 | } | 154 | } |
152 | 155 | ||
156 | // DecodeJSON decodes JSON data from r to v. | ||
157 | // Returns an error if it fails. | ||
158 | func DecodeJSON(r io.Reader, v interface{}) error { | ||
159 | return json.NewDecoder(r).Decode(v) | ||
160 | } | ||
161 | |||
162 | func GetJSON(url string, v interface{}, params url.Values, headers http.Header) (status int, err error) { | ||
163 | p := params.Encode() | ||
164 | if p != "" { | ||
165 | url += "?" + p | ||
166 | } | ||
167 | |||
168 | req, err := http.NewRequest(http.MethodGet, url, nil) | ||
169 | if err != nil { | ||
170 | return 0, err | ||
171 | } | ||
172 | |||
173 | if headers != nil { | ||
174 | for k, head := range headers { | ||
175 | for i, h := range head { | ||
176 | if i == 0 { | ||
177 | req.Header.Set(k, h) | ||
178 | } else { | ||
179 | req.Header.Add(k, h) | ||
180 | } | ||
181 | } | ||
182 | } | ||
183 | } | ||
184 | |||
185 | resp, err := http.DefaultClient.Do(req) | ||
186 | if err != nil { | ||
187 | return 0, err | ||
188 | } | ||
189 | status = resp.StatusCode | ||
190 | |||
191 | return status, DecodeJSON(resp.Body, v) | ||
192 | } | ||
193 | |||
194 | func PostJSON(url string, v, r interface{}, params url.Values, headers http.Header) (status int, err error) { | ||
195 | buffer := bytes.NewBuffer(make([]byte, 0)) | ||
196 | json.NewEncoder(buffer).Encode(v) | ||
197 | |||
198 | p := params.Encode() | ||
199 | if p != "" { | ||
200 | url += "?" + p | ||
201 | } | ||
202 | |||
203 | req, err := http.NewRequest(http.MethodPost, url, buffer) | ||
204 | if err != nil { | ||
205 | return 0, err | ||
206 | } | ||
207 | |||
208 | if headers != nil { | ||
209 | for k, head := range headers { | ||
210 | for i, h := range head { | ||
211 | if i == 0 { | ||
212 | req.Header.Set(k, h) | ||
213 | } else { | ||
214 | req.Header.Add(k, h) | ||
215 | } | ||
216 | } | ||
217 | } | ||
218 | } | ||
219 | req.Header.Set("Content-Type", "application/json") | ||
220 | |||
221 | resp, err := http.DefaultClient.Do(req) | ||
222 | if err != nil { | ||
223 | return 0, err | ||
224 | } | ||
225 | status = resp.StatusCode | ||
226 | |||
227 | return status, DecodeJSON(resp.Body, r) | ||
228 | } | ||
153 | 229 |
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" | ||
9 | "net/http" | 8 | "net/http" |
10 | "strings" | 9 | "strings" |
11 | "sync" | 10 | "sync" |
12 | "time" | 11 | "time" |
13 | ) | 12 | ) |
14 | 13 | ||
15 | var ( | 14 | var ( |
16 | mu = &sync.Mutex{} | 15 | mu = &sync.Mutex{} |
17 | metadata = make(map[string]Payload) | 16 | metadata = make(map[string]Payload) |
18 | 17 | ||
19 | updateQue = make(map[string][]byte) | 18 | updateQue = make(map[string][]byte) |
20 | 19 | ||
21 | metadataDB *sql.DB | 20 | metadataDB *sql.DB |
22 | activeProject string | 21 | activeProject string |
23 | 22 | ||
24 | inited bool | 23 | inited bool |
25 | metaDriver string | 24 | metaDriver string |
26 | ) | 25 | ) |
27 | 26 | ||
28 | // LangMap ... | 27 | // LangMap ... |
29 | type LangMap map[string]map[string]string | 28 | type LangMap map[string]map[string]string |
30 | 29 | ||
31 | // Field ... | 30 | // Field ... |
32 | type Field struct { | 31 | type Field struct { |
33 | Parameter string `json:"param"` | 32 | Parameter string `json:"param"` |
34 | Type string `json:"type"` | 33 | Type string `json:"type"` |
35 | Visible bool `json:"visible"` | 34 | Visible bool `json:"visible"` |
36 | Editable bool `json:"editable"` | 35 | Editable bool `json:"editable"` |
37 | } | 36 | } |
38 | 37 | ||
39 | // CorrelationField ... | 38 | // CorrelationField ... |
40 | type CorrelationField struct { | 39 | type CorrelationField struct { |
41 | Result string `json:"result"` | 40 | Result string `json:"result"` |
42 | Elements []string `json:"elements"` | 41 | Elements []string `json:"elements"` |
43 | Type string `json:"type"` | 42 | Type string `json:"type"` |
44 | } | 43 | } |
45 | 44 | ||
46 | // Translation ... | 45 | // Translation ... |
47 | type Translation struct { | 46 | type Translation struct { |
48 | Language string `json:"language"` | 47 | Language string `json:"language"` |
49 | FieldsLabels map[string]string `json:"fieldsLabels"` | 48 | FieldsLabels map[string]string `json:"fieldsLabels"` |
50 | } | 49 | } |
51 | 50 | ||
52 | // PaginationLinks ... | 51 | // PaginationLinks ... |
53 | type PaginationLinks struct { | 52 | type PaginationLinks struct { |
54 | Base string `json:"base"` | 53 | Base string `json:"base"` |
55 | Next string `json:"next"` | 54 | Next string `json:"next"` |
56 | Prev string `json:"prev"` | 55 | Prev string `json:"prev"` |
57 | Self string `json:"self"` | 56 | Self string `json:"self"` |
58 | } | 57 | } |
59 | 58 | ||
60 | // PaginationParameters ... | 59 | // PaginationParameters ... |
61 | type PaginationParameters struct { | 60 | type PaginationParameters struct { |
62 | URL string `json:"-"` | 61 | URL string `json:"-"` |
63 | Offset int64 `json:"offset"` | 62 | Offset int64 `json:"offset"` |
64 | Limit int64 `json:"limit"` | 63 | Limit int64 `json:"limit"` |
65 | SortBy string `json:"sortBy"` | 64 | SortBy string `json:"sortBy"` |
66 | Order string `json:"order"` | 65 | Order string `json:"order"` |
67 | } | 66 | } |
68 | 67 | ||
69 | // GetPaginationParameters ... | 68 | // GetPaginationParameters ... |
70 | // TODO(marko) | 69 | // TODO(marko) |
71 | func GetPaginationParameters(req *http.Request) (p PaginationParameters) { | 70 | func GetPaginationParameters(req *http.Request) (p PaginationParameters) { |
72 | return p | 71 | return p |
73 | } | 72 | } |
74 | 73 | ||
75 | // TODO(marko) | 74 | // TODO(marko) |
76 | func (p *PaginationParameters) paginationLinks() (links PaginationLinks) { | 75 | func (p *PaginationParameters) paginationLinks() (links PaginationLinks) { |
77 | return links | 76 | return links |
78 | } | 77 | } |
79 | 78 | ||
80 | // Payload ... | 79 | // Payload ... |
81 | type Payload struct { | 80 | type Payload struct { |
82 | Method string `json:"method"` | 81 | Method string `json:"method"` |
83 | Params map[string]string `json:"params"` | 82 | Params map[string]string `json:"params"` |
84 | Lang []Translation `json:"lang"` | 83 | Lang []Translation `json:"lang"` |
85 | Fields []Field `json:"fields"` | 84 | Fields []Field `json:"fields"` |
86 | Correlations []CorrelationField `json:"correlationFields"` | 85 | Correlations []CorrelationField `json:"correlationFields"` |
87 | IDField string `json:"idField"` | 86 | IDField string `json:"idField"` |
88 | 87 | ||
89 | // Pagination | 88 | // Pagination |
90 | Count int64 `json:"count"` | 89 | Count int64 `json:"count"` |
91 | Total int64 `json:"total"` | 90 | Total int64 `json:"total"` |
92 | Links PaginationLinks `json:"_links"` | 91 | Links PaginationLinks `json:"_links"` |
93 | 92 | ||
94 | // Data holds JSON payload. It can't be used for itteration. | 93 | // Data holds JSON payload. It can't be used for itteration. |
95 | Data interface{} `json:"data"` | 94 | Data interface{} `json:"data"` |
96 | } | 95 | } |
97 | 96 | ||
98 | func (p *Payload) addLang(code string, labels map[string]string) { | 97 | func (p *Payload) addLang(code string, labels map[string]string) { |
99 | t := Translation{ | 98 | t := Translation{ |
100 | Language: code, | 99 | Language: code, |
101 | FieldsLabels: labels, | 100 | FieldsLabels: labels, |
102 | } | 101 | } |
103 | p.Lang = append(p.Lang, t) | 102 | p.Lang = append(p.Lang, t) |
104 | } | 103 | } |
105 | 104 | ||
106 | // SetData ... | 105 | // SetData ... |
107 | func (p *Payload) SetData(data interface{}) { | 106 | func (p *Payload) SetData(data interface{}) { |
108 | p.Data = data | 107 | p.Data = data |
109 | } | 108 | } |
110 | 109 | ||
111 | // SetPaginationInfo ... | 110 | // SetPaginationInfo ... |
112 | func (p *Payload) SetPaginationInfo(count, total int64, params PaginationParameters) { | 111 | func (p *Payload) SetPaginationInfo(count, total int64, params PaginationParameters) { |
113 | p.Count = count | 112 | p.Count = count |
114 | p.Total = total | 113 | p.Total = total |
115 | p.Links = params.paginationLinks() | 114 | p.Links = params.paginationLinks() |
116 | } | 115 | } |
117 | 116 | ||
118 | // NewPayload returs a payload sceleton for entity described with key. | 117 | // NewPayload returs a payload sceleton for entity described with key. |
119 | func NewPayload(r *http.Request, key string) Payload { | 118 | func NewPayload(r *http.Request, key string) Payload { |
120 | p := metadata[key] | 119 | p := metadata[key] |
121 | p.Method = r.Method + " " + r.RequestURI | 120 | p.Method = r.Method + " " + r.RequestURI |
122 | return p | 121 | return p |
123 | } | 122 | } |
124 | 123 | ||
125 | // DecodeJSON decodes JSON data from r to v. | ||
126 | // Returns an error if it fails. | ||
127 | func DecodeJSON(r io.Reader, v interface{}) error { | ||
128 | return json.NewDecoder(r).Decode(v) | ||
129 | } | ||
130 | |||
131 | // InitPayloadsMetadata loads all payloads' information into 'metadata' variable. | 124 | // InitPayloadsMetadata loads all payloads' information into 'metadata' variable. |
132 | func InitPayloadsMetadata(drv string, db *sql.DB, project string) error { | 125 | func InitPayloadsMetadata(drv string, db *sql.DB, project string) error { |
133 | var err error | 126 | var err error |
134 | if drv != "ora" && drv != "mysql" { | 127 | if drv != "ora" && drv != "mysql" { |
135 | err = errors.New("driver not supported") | 128 | err = errors.New("driver not supported") |
136 | return err | 129 | return err |
137 | } | 130 | } |
138 | 131 | ||
139 | metaDriver = drv | 132 | metaDriver = drv |
140 | metadataDB = db | 133 | metadataDB = db |
141 | activeProject = project | 134 | activeProject = project |
142 | 135 | ||
143 | mu.Lock() | 136 | mu.Lock() |
144 | defer mu.Unlock() | 137 | defer mu.Unlock() |
145 | err = initMetadata(project) | 138 | err = initMetadata(project) |
146 | if err != nil { | 139 | if err != nil { |
147 | return err | 140 | return err |
148 | } | 141 | } |
149 | inited = true | 142 | inited = true |
150 | 143 | ||
151 | return nil | 144 | return nil |
152 | } | 145 | } |
153 | 146 | ||
154 | // EnableHotloading ... | 147 | // EnableHotloading ... |
155 | func EnableHotloading(interval int) { | 148 | func EnableHotloading(interval int) { |
156 | if interval > 0 { | 149 | if interval > 0 { |
157 | go hotload(interval) | 150 | go hotload(interval) |
158 | } | 151 | } |
159 | } | 152 | } |
160 | 153 | ||
161 | // GetMetadataForAllEntities ... | 154 | // GetMetadataForAllEntities ... |
162 | func GetMetadataForAllEntities() map[string]Payload { | 155 | func GetMetadataForAllEntities() map[string]Payload { |
163 | return metadata | 156 | return metadata |
164 | } | 157 | } |
165 | 158 | ||
166 | // GetMetadataForEntity ... | 159 | // GetMetadataForEntity ... |
167 | func GetMetadataForEntity(t string) (Payload, bool) { | 160 | func GetMetadataForEntity(t string) (Payload, bool) { |
168 | p, ok := metadata[t] | 161 | p, ok := metadata[t] |
169 | return p, ok | 162 | return p, ok |
170 | } | 163 | } |
171 | 164 | ||
172 | // QueEntityModelUpdate ... | 165 | // QueEntityModelUpdate ... |
173 | func QueEntityModelUpdate(entityType string, v interface{}) { | 166 | func QueEntityModelUpdate(entityType string, v interface{}) { |
174 | updateQue[entityType], _ = json.Marshal(v) | 167 | updateQue[entityType], _ = json.Marshal(v) |
175 | } | 168 | } |
176 | 169 | ||
177 | // UpdateEntityModels ... | 170 | // UpdateEntityModels ... |
178 | func UpdateEntityModels(command string) (total, upd, add int, err error) { | 171 | func UpdateEntityModels(command string) (total, upd, add int, err error) { |
179 | if command != "force" && command != "missing" { | 172 | if command != "force" && command != "missing" { |
180 | return total, 0, 0, errors.New("webutility: unknown command: " + command) | 173 | return total, 0, 0, errors.New("webutility: unknown command: " + command) |
181 | } | 174 | } |
182 | 175 | ||
183 | if !inited { | 176 | if !inited { |
184 | return 0, 0, 0, errors.New("webutility: metadata not initialized but update was tried") | 177 | return 0, 0, 0, errors.New("webutility: metadata not initialized but update was tried") |
185 | } | 178 | } |
186 | 179 | ||
187 | total = len(updateQue) | 180 | total = len(updateQue) |
188 | 181 | ||
189 | toUpdate := make([]string, 0) | 182 | toUpdate := make([]string, 0) |
190 | toAdd := make([]string, 0) | 183 | toAdd := make([]string, 0) |
191 | 184 | ||
192 | for k := range updateQue { | 185 | for k := range updateQue { |
193 | if _, exists := metadata[k]; exists { | 186 | if _, exists := metadata[k]; exists { |
194 | if command == "force" { | 187 | if command == "force" { |
195 | toUpdate = append(toUpdate, k) | 188 | toUpdate = append(toUpdate, k) |
196 | } | 189 | } |
197 | } else { | 190 | } else { |
198 | toAdd = append(toAdd, k) | 191 | toAdd = append(toAdd, k) |
199 | } | 192 | } |
200 | } | 193 | } |
201 | 194 | ||
202 | var uStmt *sql.Stmt | 195 | var uStmt *sql.Stmt |
203 | if metaDriver == "ora" { | 196 | if metaDriver == "ora" { |
204 | uStmt, err = metadataDB.Prepare("update entities set entity_model = :1 where entity_type = :2") | 197 | uStmt, err = metadataDB.Prepare("update entities set entity_model = :1 where entity_type = :2") |
205 | if err != nil { | 198 | if err != nil { |
206 | return | 199 | return |
207 | } | 200 | } |
208 | } else if metaDriver == "mysql" { | 201 | } else if metaDriver == "mysql" { |
209 | uStmt, err = metadataDB.Prepare("update entities set entity_model = ? where entity_type = ?") | 202 | uStmt, err = metadataDB.Prepare("update entities set entity_model = ? where entity_type = ?") |
210 | if err != nil { | 203 | if err != nil { |
211 | return | 204 | return |
212 | } | 205 | } |
213 | } | 206 | } |
214 | for _, k := range toUpdate { | 207 | for _, k := range toUpdate { |
215 | _, err = uStmt.Exec(string(updateQue[k]), k) | 208 | _, err = uStmt.Exec(string(updateQue[k]), k) |
216 | if err != nil { | 209 | if err != nil { |
217 | return | 210 | return |
218 | } | 211 | } |
219 | upd++ | 212 | upd++ |
220 | } | 213 | } |
221 | 214 | ||
222 | blankPayload, _ := json.Marshal(Payload{}) | 215 | blankPayload, _ := json.Marshal(Payload{}) |
223 | var iStmt *sql.Stmt | 216 | var iStmt *sql.Stmt |
224 | if metaDriver == "ora" { | 217 | if metaDriver == "ora" { |
225 | iStmt, err = metadataDB.Prepare("insert into entities(projekat, metadata, entity_type, entity_model) values(:1, :2, :3, :4)") | 218 | iStmt, err = metadataDB.Prepare("insert into entities(projekat, metadata, entity_type, entity_model) values(:1, :2, :3, :4)") |
226 | if err != nil { | 219 | if err != nil { |
227 | return | 220 | return |
228 | } | 221 | } |
229 | } else if metaDriver == "mysql" { | 222 | } else if metaDriver == "mysql" { |
230 | iStmt, err = metadataDB.Prepare("insert into entities(projekat, metadata, entity_type, entity_model) values(?, ?, ?, ?)") | 223 | iStmt, err = metadataDB.Prepare("insert into entities(projekat, metadata, entity_type, entity_model) values(?, ?, ?, ?)") |
231 | if err != nil { | 224 | if err != nil { |
232 | return | 225 | return |
233 | } | 226 | } |
234 | } | 227 | } |
235 | for _, k := range toAdd { | 228 | for _, k := range toAdd { |
236 | _, err = iStmt.Exec(activeProject, string(blankPayload), k, string(updateQue[k])) | 229 | _, err = iStmt.Exec(activeProject, string(blankPayload), k, string(updateQue[k])) |
237 | if err != nil { | 230 | if err != nil { |
238 | return | 231 | return |
239 | } | 232 | } |
240 | metadata[k] = Payload{} | 233 | metadata[k] = Payload{} |
241 | add++ | 234 | add++ |
242 | } | 235 | } |
243 | 236 | ||
244 | return total, upd, add, nil | 237 | return total, upd, add, nil |
245 | } | 238 | } |
246 | 239 | ||
247 | func initMetadata(project string) error { | 240 | func initMetadata(project string) error { |
248 | rows, err := metadataDB.Query(`select | 241 | rows, err := metadataDB.Query(`select |
249 | entity_type, | 242 | entity_type, |
250 | metadata | 243 | metadata |
251 | from entities | 244 | from entities |
252 | where projekat = ` + fmt.Sprintf("'%s'", project)) | 245 | where projekat = ` + fmt.Sprintf("'%s'", project)) |
253 | if err != nil { | 246 | if err != nil { |
254 | return err | 247 | return err |
255 | } | 248 | } |
256 | defer rows.Close() | 249 | defer rows.Close() |
257 | 250 | ||
258 | if len(metadata) > 0 { | 251 | if len(metadata) > 0 { |
259 | metadata = nil | 252 | metadata = nil |
260 | } | 253 | } |
261 | metadata = make(map[string]Payload) | 254 | metadata = make(map[string]Payload) |
262 | for rows.Next() { | 255 | for rows.Next() { |
263 | var name, load string | 256 | var name, load string |
264 | rows.Scan(&name, &load) | 257 | rows.Scan(&name, &load) |
265 | 258 | ||
266 | p := Payload{} | 259 | p := Payload{} |
267 | err := json.Unmarshal([]byte(load), &p) | 260 | err := json.Unmarshal([]byte(load), &p) |
268 | if err != nil { | 261 | if err != nil { |
269 | fmt.Printf("webutility: couldn't init: '%s' metadata: %s:\n%s\n", name, err.Error(), load) | 262 | fmt.Printf("webutility: couldn't init: '%s' metadata: %s:\n%s\n", name, err.Error(), load) |
270 | } else { | 263 | } else { |
271 | metadata[name] = p | 264 | metadata[name] = p |
272 | } | 265 | } |
273 | } | 266 | } |
274 | 267 | ||
275 | return nil | 268 | return nil |
276 | } | 269 | } |
277 | 270 | ||
278 | // LoadMetadataFromFile expects file in format: | 271 | // LoadMetadataFromFile expects file in format: |
279 | // | 272 | // |
280 | // [ payload A identifier ] | 273 | // [ payload A identifier ] |
281 | // key1 = value1 | 274 | // key1 = value1 |
282 | // key2 = value2 | 275 | // key2 = value2 |
283 | // ... | 276 | // ... |
284 | // [ payload B identifier ] | 277 | // [ payload B identifier ] |
285 | // key1 = value1 | 278 | // key1 = value1 |
286 | // key2 = value2 | 279 | // key2 = value2 |
287 | // ... | 280 | // ... |
288 | // | 281 | // |
289 | // TODO(marko): Currently supports only one hardcoded language... | 282 | // TODO(marko): Currently supports only one hardcoded language... |
290 | func LoadMetadataFromFile(path string) error { | 283 | func LoadMetadataFromFile(path string) error { |
291 | lines, err := ReadFileLines(path) | 284 | lines, err := ReadFileLines(path) |
292 | if err != nil { | 285 | if err != nil { |
293 | return err | 286 | return err |
294 | } | 287 | } |
295 | 288 | ||
296 | metadata = make(map[string]Payload) | 289 | metadata = make(map[string]Payload) |
297 | 290 | ||
298 | var name string | 291 | var name string |
299 | for i, l := range lines { | 292 | for i, l := range lines { |
300 | // skip empty lines | 293 | // skip empty lines |
301 | if l = strings.TrimSpace(l); len(l) == 0 { | 294 | if l = strings.TrimSpace(l); len(l) == 0 { |
302 | continue | 295 | continue |
303 | } | 296 | } |
304 | 297 | ||
305 | if IsWrappedWith(l, "[", "]") { | 298 | if IsWrappedWith(l, "[", "]") { |
306 | name = strings.Trim(l, "[]") | 299 | name = strings.Trim(l, "[]") |
307 | p := Payload{} | 300 | p := Payload{} |
308 | p.addLang("sr", make(map[string]string)) | 301 | p.addLang("sr", make(map[string]string)) |
309 | metadata[name] = p | 302 | metadata[name] = p |
310 | continue | 303 | continue |
311 | } | 304 | } |
312 | 305 | ||
313 | if name == "" { | 306 | if name == "" { |
314 | return fmt.Errorf("webutility: LoadMetadataFromFile: error on line %d: [no header] [%s]", i+1, l) | 307 | return fmt.Errorf("webutility: LoadMetadataFromFile: error on line %d: [no header] [%s]", i+1, l) |
315 | } | 308 | } |
316 | 309 | ||
317 | parts := strings.Split(l, "=") | 310 | parts := strings.Split(l, "=") |
318 | if len(parts) != 2 { | 311 | if len(parts) != 2 { |
319 | return fmt.Errorf("webutility: LoadMetadataFromFile: error on line %d: [invalid format] [%s]", i+1, l) | 312 | return fmt.Errorf("webutility: LoadMetadataFromFile: error on line %d: [invalid format] [%s]", i+1, l) |
320 | } | 313 | } |
321 | 314 | ||
322 | k := strings.TrimSpace(parts[0]) | 315 | k := strings.TrimSpace(parts[0]) |
323 | v := strings.TrimSpace(parts[1]) | 316 | v := strings.TrimSpace(parts[1]) |
324 | if v != "-" { | 317 | if v != "-" { |
325 | metadata[name].Lang[0].FieldsLabels[k] = v | 318 | metadata[name].Lang[0].FieldsLabels[k] = v |
326 | } | 319 | } |
327 | } | 320 | } |
328 | 321 | ||
329 | return nil | 322 | return nil |
330 | } | 323 | } |
331 | 324 | ||
332 | func hotload(n int) { | 325 | func hotload(n int) { |
333 | entityScan := make(map[string]int64) | 326 | entityScan := make(map[string]int64) |
334 | firstCheck := true | 327 | firstCheck := true |
335 | for { | 328 | for { |
336 | time.Sleep(time.Duration(n) * time.Second) | 329 | time.Sleep(time.Duration(n) * time.Second) |
337 | rows, err := metadataDB.Query(`select | 330 | rows, err := metadataDB.Query(`select |
338 | ora_rowscn, | 331 | ora_rowscn, |
339 | entity_type | 332 | entity_type |
340 | from entities where projekat = ` + fmt.Sprintf("'%s'", activeProject)) | 333 | from entities where projekat = ` + fmt.Sprintf("'%s'", activeProject)) |
341 | if err != nil { | 334 | if err != nil { |
342 | fmt.Printf("webutility: hotload failed: %v\n", err) | 335 | fmt.Printf("webutility: hotload failed: %v\n", err) |
343 | time.Sleep(time.Duration(n) * time.Second) | 336 | time.Sleep(time.Duration(n) * time.Second) |
344 | continue | 337 | continue |
345 | } | 338 | } |
346 | 339 | ||
347 | var toRefresh []string | 340 | var toRefresh []string |
348 | for rows.Next() { | 341 | for rows.Next() { |
349 | var scanID int64 | 342 | var scanID int64 |
350 | var entity string | 343 | var entity string |
351 | rows.Scan(&scanID, &entity) | 344 | rows.Scan(&scanID, &entity) |
352 | oldID, ok := entityScan[entity] | 345 | oldID, ok := entityScan[entity] |
353 | if !ok || oldID != scanID { | 346 | if !ok || oldID != scanID { |
354 | entityScan[entity] = scanID | 347 | entityScan[entity] = scanID |
355 | toRefresh = append(toRefresh, entity) | 348 | toRefresh = append(toRefresh, entity) |
356 | } | 349 | } |
357 | } | 350 | } |
358 | rows.Close() | 351 | rows.Close() |
359 | 352 | ||
360 | if rows.Err() != nil { | 353 | if rows.Err() != nil { |
361 | fmt.Printf("webutility: hotload rset error: %v\n", rows.Err()) | 354 | fmt.Printf("webutility: hotload rset error: %v\n", rows.Err()) |
362 | time.Sleep(time.Duration(n) * time.Second) | 355 | time.Sleep(time.Duration(n) * time.Second) |
363 | continue | 356 | continue |
364 | } | 357 | } |
365 | 358 | ||
366 | if len(toRefresh) > 0 && !firstCheck { | 359 | if len(toRefresh) > 0 && !firstCheck { |
367 | mu.Lock() | 360 | mu.Lock() |
368 | refreshMetadata(toRefresh) | 361 | refreshMetadata(toRefresh) |
369 | mu.Unlock() | 362 | mu.Unlock() |
370 | } | 363 | } |
371 | if firstCheck { | 364 | if firstCheck { |
372 | firstCheck = false | 365 | firstCheck = false |
373 | } | 366 | } |
374 | } | 367 | } |
375 | } | 368 | } |
376 | 369 | ||
377 | func refreshMetadata(entities []string) { | 370 | func refreshMetadata(entities []string) { |
378 | for _, e := range entities { | 371 | for _, e := range entities { |
379 | fmt.Printf("refreshing %s\n", e) | 372 | fmt.Printf("refreshing %s\n", e) |
380 | rows, err := metadataDB.Query(`select | 373 | rows, err := metadataDB.Query(`select |
381 | metadata | 374 | metadata |
382 | from entities | 375 | from entities |
383 | where projekat = ` + fmt.Sprintf("'%s'", activeProject) + | 376 | where projekat = ` + fmt.Sprintf("'%s'", activeProject) + |
384 | ` and entity_type = ` + fmt.Sprintf("'%s'", e)) | 377 | ` and entity_type = ` + fmt.Sprintf("'%s'", e)) |
385 | 378 | ||
386 | if err != nil { | 379 | if err != nil { |
387 | fmt.Printf("webutility: refresh: prep: %v\n", err) | 380 | fmt.Printf("webutility: refresh: prep: %v\n", err) |
388 | rows.Close() | 381 | rows.Close() |
389 | continue | 382 | continue |
390 | } | 383 | } |
391 | 384 | ||
392 | for rows.Next() { | 385 | for rows.Next() { |
393 | var load string | 386 | var load string |
394 | rows.Scan(&load) | 387 | rows.Scan(&load) |
395 | p := Payload{} | 388 | p := Payload{} |
396 | err := json.Unmarshal([]byte(load), &p) | 389 | err := json.Unmarshal([]byte(load), &p) |
397 | if err != nil { | 390 | if err != nil { |
398 | fmt.Printf("webutility: couldn't refresh: '%s' metadata: %s\n%s\n", e, err.Error(), load) | 391 | fmt.Printf("webutility: couldn't refresh: '%s' metadata: %s\n%s\n", e, err.Error(), load) |
399 | } else { | 392 | } else { |
400 | metadata[e] = p | 393 | metadata[e] = p |
401 | } | 394 | } |
402 | } | 395 | } |
403 | rows.Close() | 396 | rows.Close() |
404 | } | 397 | } |
405 | } | 398 | } |
406 | 399 | ||
407 | /* | 400 | /* |
408 | func ModifyMetadataForEntity(entityType string, p *Payload) error { | 401 | func ModifyMetadataForEntity(entityType string, p *Payload) error { |
409 | md, err := json.Marshal(*p) | 402 | md, err := json.Marshal(*p) |
410 | if err != nil { | 403 | if err != nil { |
411 | return err | 404 | return err |
412 | } | 405 | } |
413 | 406 | ||
414 | mu.Lock() | 407 | mu.Lock() |
415 | defer mu.Unlock() | 408 | defer mu.Unlock() |
416 | _, err = metadataDB.PrepAndExe(`update entities set | 409 | _, err = metadataDB.PrepAndExe(`update entities set |
417 | metadata = :1 | 410 | metadata = :1 |
418 | where projekat = :2 | 411 | where projekat = :2 |
419 | and entity_type = :3`, | 412 | and entity_type = :3`, |
420 | string(md), | 413 | string(md), |
421 | activeProject, | 414 | activeProject, |
422 | entityType) | 415 | entityType) |
423 | if err != nil { | 416 | if err != nil { |
424 | return err | 417 | return err |
425 | } | 418 | } |
426 | return nil | 419 | return nil |
427 | } | 420 | } |
428 | 421 | ||
429 | func DeleteEntityModel(entityType string) error { | 422 | func DeleteEntityModel(entityType string) error { |
430 | _, err := metadataDB.PrepAndExe("delete from entities where entity_type = :1", entityType) | 423 | _, err := metadataDB.PrepAndExe("delete from entities where entity_type = :1", entityType) |
431 | if err == nil { | 424 | if err == nil { |
432 | mu.Lock() | 425 | mu.Lock() |
433 | delete(metadata, entityType) | 426 | delete(metadata, entityType) |
434 | mu.Unlock() | 427 | mu.Unlock() |
435 | } | 428 | } |
436 | return err | 429 | return err |
437 | } | 430 | } |
438 | */ | 431 | */ |
439 | 432 |