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  }