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