github.com/jancarloviray/community@v0.41.1-0.20170124221257-33a66c87cf2f/core/section/provider/provider.go (about) 1 // Copyright 2016 Documize Inc. <legal@documize.com>. All rights reserved. 2 // 3 // This software (Documize Community Edition) is licensed under 4 // GNU AGPL v3 http://www.gnu.org/licenses/agpl-3.0.en.html 5 // 6 // You can operate outside the AGPL restrictions by purchasing 7 // Documize Enterprise Edition and obtaining a commercial license 8 // by contacting <sales@documize.com>. 9 // 10 // https://documize.com 11 12 package provider 13 14 import ( 15 "encoding/json" 16 "errors" 17 "fmt" 18 "net/http" 19 "sort" 20 "strings" 21 22 "github.com/documize/community/core/api/request" 23 "github.com/documize/community/core/log" 24 ) 25 26 // SecretReplacement is a constant used to replace secrets in data-structures when required. 27 // 8 stars. 28 const SecretReplacement = "********" 29 30 // sectionsMap is where individual sections register themselves. 31 var sectionsMap = make(map[string]Provider) 32 33 // TypeMeta details a "smart section" that represents a "page" in a document. 34 type TypeMeta struct { 35 ID string `json:"id"` 36 Order int `json:"order"` 37 ContentType string `json:"contentType"` 38 PageType string `json:"pageType"` 39 Title string `json:"title"` 40 Description string `json:"description"` 41 Preview bool `json:"preview"` // coming soon! 42 Callback func(http.ResponseWriter, *http.Request) error `json:"-"` 43 } 44 45 // ConfigHandle returns the key name for database config table 46 func (t *TypeMeta) ConfigHandle() string { 47 return fmt.Sprintf("SECTION-%s", strings.ToUpper(t.ContentType)) 48 } 49 50 // Provider represents a 'page' in a document. 51 type Provider interface { 52 Meta() TypeMeta // Meta returns section details 53 Command(ctx *Context, w http.ResponseWriter, r *http.Request) // Command is general-purpose method that can return data to UI 54 Render(ctx *Context, config, data string) string // Render converts section data into presentable HTML 55 Refresh(ctx *Context, config, data string) string // Refresh returns latest data 56 } 57 58 // Context describes the environment the section code runs in 59 type Context struct { 60 OrgID string 61 UserID string 62 prov Provider 63 inCommand bool 64 } 65 66 // NewContext is a convenience function. 67 func NewContext(orgid, userid string) *Context { 68 if orgid == "" || userid == "" { 69 log.Error("NewContext incorrect orgid:"+orgid+" userid:"+userid, errors.New("bad section context")) 70 } 71 return &Context{OrgID: orgid, UserID: userid} 72 } 73 74 // Register makes document section type available 75 func Register(name string, p Provider) { 76 sectionsMap[name] = p 77 } 78 79 // List returns available types 80 func List() map[string]Provider { 81 return sectionsMap 82 } 83 84 // GetSectionMeta returns a list of smart sections. 85 func GetSectionMeta() []TypeMeta { 86 sections := []TypeMeta{} 87 88 for _, section := range sectionsMap { 89 sections = append(sections, section.Meta()) 90 } 91 92 return sortSections(sections) 93 } 94 95 // Command passes parameters to the given section id, the returned bool indicates success. 96 func Command(section string, ctx *Context, w http.ResponseWriter, r *http.Request) bool { 97 s, ok := sectionsMap[section] 98 if ok { 99 ctx.prov = s 100 ctx.inCommand = true 101 s.Command(ctx, w, r) 102 } 103 return ok 104 } 105 106 // Callback passes parameters to the given section callback, the returned error indicates success. 107 func Callback(section string, w http.ResponseWriter, r *http.Request) error { 108 s, ok := sectionsMap[section] 109 if ok { 110 if cb := s.Meta().Callback; cb != nil { 111 return cb(w, r) 112 } 113 } 114 return errors.New("section not found") 115 } 116 117 // Render runs that operation for the given section id, the returned bool indicates success. 118 func Render(section string, ctx *Context, config, data string) (string, bool) { 119 s, ok := sectionsMap[section] 120 if ok { 121 ctx.prov = s 122 return s.Render(ctx, config, data), true 123 } 124 return "", false 125 } 126 127 // Refresh returns the latest data for a section. 128 func Refresh(section string, ctx *Context, config, data string) (string, bool) { 129 s, ok := sectionsMap[section] 130 if ok { 131 ctx.prov = s 132 return s.Refresh(ctx, config, data), true 133 } 134 return "", false 135 } 136 137 // WriteJSON writes data as JSON to HTTP response. 138 func WriteJSON(w http.ResponseWriter, v interface{}) { 139 w.Header().Set("Content-Type", "application/json; charset=utf-8") 140 w.WriteHeader(http.StatusOK) 141 142 j, err := json.Marshal(v) 143 144 if err != nil { 145 WriteMarshalError(w, err) 146 return 147 } 148 149 _, err = w.Write(j) 150 log.IfErr(err) 151 } 152 153 // WriteString writes string tp HTTP response. 154 func WriteString(w http.ResponseWriter, data string) { 155 w.WriteHeader(http.StatusOK) 156 _, err := w.Write([]byte(data)) 157 log.IfErr(err) 158 } 159 160 // WriteEmpty returns just OK to HTTP response. 161 func WriteEmpty(w http.ResponseWriter) { 162 w.Header().Set("Content-Type", "application/json; charset=utf-8") 163 w.WriteHeader(http.StatusOK) 164 _, err := w.Write([]byte("{}")) 165 log.IfErr(err) 166 } 167 168 // WriteMarshalError write JSON marshalling error to HTTP response. 169 func WriteMarshalError(w http.ResponseWriter, err error) { 170 w.Header().Set("Content-Type", "application/json; charset=utf-8") 171 w.WriteHeader(http.StatusBadRequest) 172 _, err2 := w.Write([]byte("{Error: 'JSON marshal failed'}")) 173 log.IfErr(err2) 174 log.Error("JSON marshall failed", err) 175 } 176 177 // WriteMessage write string to HTTP response. 178 func WriteMessage(w http.ResponseWriter, section, msg string) { 179 w.Header().Set("Content-Type", "application/json; charset=utf-8") 180 w.WriteHeader(http.StatusBadRequest) 181 _, err := w.Write([]byte("{Message: " + msg + "}")) 182 log.IfErr(err) 183 log.Info(fmt.Sprintf("Error for section %s: %s", section, msg)) 184 } 185 186 // WriteError write given error to HTTP response. 187 func WriteError(w http.ResponseWriter, section string, err error) { 188 w.Header().Set("Content-Type", "application/json; charset=utf-8") 189 w.WriteHeader(http.StatusBadRequest) 190 _, err2 := w.Write([]byte("{Error: 'Internal server error'}")) 191 log.IfErr(err2) 192 log.Error(fmt.Sprintf("Error for section %s", section), err) 193 } 194 195 // WriteForbidden write 403 to HTTP response. 196 func WriteForbidden(w http.ResponseWriter) { 197 w.Header().Set("Content-Type", "application/json; charset=utf-8") 198 w.WriteHeader(http.StatusForbidden) 199 _, err := w.Write([]byte("{Error: 'Unauthorized'}")) 200 log.IfErr(err) 201 } 202 203 // Secrets handling 204 205 // SaveSecrets for the current user/org combination. 206 // The secrets must be in the form of a JSON format string, for example `{"mysecret":"lover"}`. 207 // An empty string signifies no valid secrets for this user/org combination. 208 // Note that this function can only be called within the Command method of a section. 209 func (c *Context) SaveSecrets(JSONobj string) error { 210 if !c.inCommand { 211 return errors.New("SaveSecrets() may only be called from within Command()") 212 } 213 m := c.prov.Meta() 214 return request.UserConfigSetJSON(c.OrgID, c.UserID, m.ContentType, JSONobj) 215 } 216 217 // MarshalSecrets to the database. 218 // Parameter the same as for json.Marshal(). 219 func (c *Context) MarshalSecrets(sec interface{}) error { 220 if !c.inCommand { 221 return errors.New("MarshalSecrets() may only be called from within Command()") 222 } 223 byts, err := json.Marshal(sec) 224 if err != nil { 225 return err 226 } 227 return c.SaveSecrets(string(byts)) 228 } 229 230 // GetSecrets for the current context user/org. 231 // For example (see SaveSecrets example): thisContext.GetSecrets("mysecret") 232 // JSONpath format is defined at https://dev.mysql.com/doc/refman/5.7/en/json-path-syntax.html . 233 // An empty JSONpath returns the whole JSON object, as JSON. 234 // Errors return the empty string. 235 func (c *Context) GetSecrets(JSONpath string) string { 236 m := c.prov.Meta() 237 return request.UserConfigGetJSON(c.OrgID, c.UserID, m.ContentType, JSONpath) 238 } 239 240 // ErrNoSecrets is returned if no secret is found in the database. 241 var ErrNoSecrets = errors.New("no secrets in database") 242 243 // UnmarshalSecrets from the database. 244 // Parameter the same as for "v" in json.Unmarshal(). 245 func (c *Context) UnmarshalSecrets(v interface{}) error { 246 secTxt := c.GetSecrets("") // get all the json of the secrets 247 if len(secTxt) > 0 { 248 return json.Unmarshal([]byte(secTxt), v) 249 } 250 return ErrNoSecrets 251 } 252 253 // sort sections in order that that should be presented. 254 type sectionsToSort []TypeMeta 255 256 func (s sectionsToSort) Len() int { return len(s) } 257 func (s sectionsToSort) Swap(i, j int) { s[i], s[j] = s[j], s[i] } 258 func (s sectionsToSort) Less(i, j int) bool { 259 if s[i].Order == s[j].Order { 260 return s[i].Title < s[j].Title 261 } 262 return s[i].Order > s[j].Order 263 } 264 265 func sortSections(in []TypeMeta) []TypeMeta { 266 sts := sectionsToSort(in) 267 sort.Sort(sts) 268 return []TypeMeta(sts) 269 }