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  }