github.com/rpdict/ponzu@v0.10.1-0.20190226054626-477f29d6bf5e/system/item/item.go (about)

     1  // Package item provides the default functionality to Ponzu's content/data types,
     2  // how they interact with the API, and how to override or enhance their abilities
     3  // using various interfaces.
     4  package item
     5  
     6  import (
     7  	"fmt"
     8  	"log"
     9  	"net/http"
    10  	"os"
    11  	"regexp"
    12  	"strings"
    13  	"unicode"
    14  
    15  	"github.com/blevesearch/bleve"
    16  	"github.com/blevesearch/bleve/mapping"
    17  	"github.com/gofrs/uuid"
    18  	"golang.org/x/text/transform"
    19  	"golang.org/x/text/unicode/norm"
    20  )
    21  
    22  var rxList map[*regexp.Regexp][]byte
    23  
    24  func init() {
    25  	// Compile regex once to use in stringToSlug().
    26  	// We store the compiled regex as the key
    27  	// and assign the replacement as the map's value.
    28  	rxList = map[*regexp.Regexp][]byte{
    29  		regexp.MustCompile("`[-]+`"):                  []byte("-"),
    30  		regexp.MustCompile("[[:space:]]"):             []byte("-"),
    31  		regexp.MustCompile("[[:blank:]]"):             []byte(""),
    32  		regexp.MustCompile("`[^a-z0-9]`i"):            []byte("-"),
    33  		regexp.MustCompile("[!/:-@[-`{-~]"):           []byte(""),
    34  		regexp.MustCompile("/[^\x20-\x7F]/"):          []byte(""),
    35  		regexp.MustCompile("`&(amp;)?#?[a-z0-9]+;`i"): []byte("-"),
    36  		regexp.MustCompile("`&([a-z])(acute|uml|circ|grave|ring|cedil|slash|tilde|caron|lig|quot|rsquo);`i"): []byte("\\1"),
    37  	}
    38  }
    39  
    40  // Sluggable makes a struct locatable by URL with it's own path.
    41  // As an Item implementing Sluggable, slugs may overlap. If this is an issue,
    42  // make your content struct (or one which embeds Item) implement Sluggable
    43  // and it will override the slug created by Item's SetSlug with your own
    44  type Sluggable interface {
    45  	SetSlug(string)
    46  	ItemSlug() string
    47  }
    48  
    49  // Identifiable enables a struct to have its ID set/get. Typically this is done
    50  // to set an ID to -1 indicating it is new for DB inserts, since by default
    51  // a newly initialized struct would have an ID of 0, the int zero-value, and
    52  // BoltDB's starting key per bucket is 0, thus overwriting the first record.
    53  type Identifiable interface {
    54  	ItemID() int
    55  	SetItemID(int)
    56  	UniqueID() uuid.UUID
    57  	String() string
    58  }
    59  
    60  // Sortable ensures data is sortable by time
    61  type Sortable interface {
    62  	Time() int64
    63  	Touch() int64
    64  }
    65  
    66  // Hookable provides our user with an easy way to intercept or add functionality
    67  // to the different lifecycles/events a struct may encounter. Item implements
    68  // Hookable with no-ops so our user can override only whichever ones necessary.
    69  type Hookable interface {
    70  	BeforeAPICreate(http.ResponseWriter, *http.Request) error
    71  	AfterAPICreate(http.ResponseWriter, *http.Request) error
    72  
    73  	BeforeAPIUpdate(http.ResponseWriter, *http.Request) error
    74  	AfterAPIUpdate(http.ResponseWriter, *http.Request) error
    75  
    76  	BeforeAPIDelete(http.ResponseWriter, *http.Request) error
    77  	AfterAPIDelete(http.ResponseWriter, *http.Request) error
    78  
    79  	BeforeAdminCreate(http.ResponseWriter, *http.Request) error
    80  	AfterAdminCreate(http.ResponseWriter, *http.Request) error
    81  
    82  	BeforeAdminUpdate(http.ResponseWriter, *http.Request) error
    83  	AfterAdminUpdate(http.ResponseWriter, *http.Request) error
    84  
    85  	BeforeAdminDelete(http.ResponseWriter, *http.Request) error
    86  	AfterAdminDelete(http.ResponseWriter, *http.Request) error
    87  
    88  	BeforeSave(http.ResponseWriter, *http.Request) error
    89  	AfterSave(http.ResponseWriter, *http.Request) error
    90  
    91  	BeforeDelete(http.ResponseWriter, *http.Request) error
    92  	AfterDelete(http.ResponseWriter, *http.Request) error
    93  
    94  	BeforeApprove(http.ResponseWriter, *http.Request) error
    95  	AfterApprove(http.ResponseWriter, *http.Request) error
    96  
    97  	BeforeReject(http.ResponseWriter, *http.Request) error
    98  	AfterReject(http.ResponseWriter, *http.Request) error
    99  
   100  	// Enable/Disable used for addons
   101  	BeforeEnable(http.ResponseWriter, *http.Request) error
   102  	AfterEnable(http.ResponseWriter, *http.Request) error
   103  
   104  	BeforeDisable(http.ResponseWriter, *http.Request) error
   105  	AfterDisable(http.ResponseWriter, *http.Request) error
   106  }
   107  
   108  // Hideable lets a user keep items hidden
   109  type Hideable interface {
   110  	Hide(http.ResponseWriter, *http.Request) error
   111  }
   112  
   113  // Pushable lets a user define which values of certain struct fields are
   114  // 'pushed' down to  a client via HTTP/2 Server Push. All items in the slice
   115  // should be the json tag names of the struct fields to which they correspond.
   116  type Pushable interface {
   117  	// the values contained by fields returned by Push must strictly be URL paths
   118  	Push(http.ResponseWriter, *http.Request) ([]string, error)
   119  }
   120  
   121  // Omittable lets a user define certin fields within a content struct to remove
   122  // from an API response. Helpful when you want data in the CMS, but not entirely
   123  // shown or available from the content API. All items in the slice should be the
   124  // json tag names of the struct fields to which they correspond.
   125  type Omittable interface {
   126  	Omit(http.ResponseWriter, *http.Request) ([]string, error)
   127  }
   128  
   129  // Item should only be embedded into content type structs.
   130  type Item struct {
   131  	UUID      uuid.UUID `json:"uuid"`
   132  	ID        int       `json:"id"`
   133  	Slug      string    `json:"slug"`
   134  	Timestamp int64     `json:"timestamp"`
   135  	Updated   int64     `json:"updated"`
   136  }
   137  
   138  // Time partially implements the Sortable interface
   139  func (i Item) Time() int64 {
   140  	return i.Timestamp
   141  }
   142  
   143  // Touch partially implements the Sortable interface
   144  func (i Item) Touch() int64 {
   145  	return i.Updated
   146  }
   147  
   148  // SetSlug sets the item's slug for its URL
   149  func (i *Item) SetSlug(slug string) {
   150  	i.Slug = slug
   151  }
   152  
   153  // ItemSlug sets the item's slug for its URL
   154  func (i *Item) ItemSlug() string {
   155  	return i.Slug
   156  }
   157  
   158  // ItemID gets the Item's ID field
   159  // partially implements the Identifiable interface
   160  func (i Item) ItemID() int {
   161  	return i.ID
   162  }
   163  
   164  // SetItemID sets the Item's ID field
   165  // partially implements the Identifiable interface
   166  func (i *Item) SetItemID(id int) {
   167  	i.ID = id
   168  }
   169  
   170  // UniqueID gets the Item's UUID field
   171  // partially implements the Identifiable interface
   172  func (i Item) UniqueID() uuid.UUID {
   173  	return i.UUID
   174  }
   175  
   176  // String formats an Item into a printable value
   177  // partially implements the Identifiable interface
   178  func (i Item) String() string {
   179  	return fmt.Sprintf("Item ID: %s", i.UniqueID())
   180  }
   181  
   182  // BeforeAPICreate is a no-op to ensure structs which embed Item implement Hookable
   183  func (i Item) BeforeAPICreate(res http.ResponseWriter, req *http.Request) error {
   184  	return nil
   185  }
   186  
   187  // AfterAPICreate is a no-op to ensure structs which embed Item implement Hookable
   188  func (i Item) AfterAPICreate(res http.ResponseWriter, req *http.Request) error {
   189  	return nil
   190  }
   191  
   192  // BeforeAPIUpdate is a no-op to ensure structs which embed Item implement Hookable
   193  func (i Item) BeforeAPIUpdate(res http.ResponseWriter, req *http.Request) error {
   194  	return nil
   195  }
   196  
   197  // AfterAPIUpdate is a no-op to ensure structs which embed Item implement Hookable
   198  func (i Item) AfterAPIUpdate(res http.ResponseWriter, req *http.Request) error {
   199  	return nil
   200  }
   201  
   202  // BeforeAPIDelete is a no-op to ensure structs which embed Item implement Hookable
   203  func (i Item) BeforeAPIDelete(res http.ResponseWriter, req *http.Request) error {
   204  	return nil
   205  }
   206  
   207  // AfterAPIDelete is a no-op to ensure structs which embed Item implement Hookable
   208  func (i Item) AfterAPIDelete(res http.ResponseWriter, req *http.Request) error {
   209  	return nil
   210  }
   211  
   212  // BeforeAdminCreate is a no-op to ensure structs which embed Item implement Hookable
   213  func (i Item) BeforeAdminCreate(res http.ResponseWriter, req *http.Request) error {
   214  	return nil
   215  }
   216  
   217  // AfterAdminCreate is a no-op to ensure structs which embed Item implement Hookable
   218  func (i Item) AfterAdminCreate(res http.ResponseWriter, req *http.Request) error {
   219  	return nil
   220  }
   221  
   222  // BeforeAdminUpdate is a no-op to ensure structs which embed Item implement Hookable
   223  func (i Item) BeforeAdminUpdate(res http.ResponseWriter, req *http.Request) error {
   224  	return nil
   225  }
   226  
   227  // AfterAdminUpdate is a no-op to ensure structs which embed Item implement Hookable
   228  func (i Item) AfterAdminUpdate(res http.ResponseWriter, req *http.Request) error {
   229  	return nil
   230  }
   231  
   232  // BeforeAdminDelete is a no-op to ensure structs which embed Item implement Hookable
   233  func (i Item) BeforeAdminDelete(res http.ResponseWriter, req *http.Request) error {
   234  	return nil
   235  }
   236  
   237  // AfterAdminDelete is a no-op to ensure structs which embed Item implement Hookable
   238  func (i Item) AfterAdminDelete(res http.ResponseWriter, req *http.Request) error {
   239  	return nil
   240  }
   241  
   242  // BeforeSave is a no-op to ensure structs which embed Item implement Hookable
   243  func (i Item) BeforeSave(res http.ResponseWriter, req *http.Request) error {
   244  	return nil
   245  }
   246  
   247  // AfterSave is a no-op to ensure structs which embed Item implement Hookable
   248  func (i Item) AfterSave(res http.ResponseWriter, req *http.Request) error {
   249  	return nil
   250  }
   251  
   252  // BeforeDelete is a no-op to ensure structs which embed Item implement Hookable
   253  func (i Item) BeforeDelete(res http.ResponseWriter, req *http.Request) error {
   254  	return nil
   255  }
   256  
   257  // AfterDelete is a no-op to ensure structs which embed Item implement Hookable
   258  func (i Item) AfterDelete(res http.ResponseWriter, req *http.Request) error {
   259  	fmt.Println(req.FormValue("path"))
   260  	formPath := req.FormValue("path")
   261  	dir := "/go/src/github.com/nilslice/reviews/"
   262  	pathSlice := strings.Split(formPath, "/")[2:]
   263  	path := dir + strings.Join(pathSlice, "/") //源文件路径
   264  	fmt.Println(path)
   265  	err := os.Remove(path) //删除文件
   266  	if err != nil {
   267  		//如果删除失败则输出 file remove Error!
   268  		log.Println("file remove Error!", err)
   269  		return err
   270  	} else {
   271  		//如果删除成功则输出 file remove OK!
   272  		fmt.Print("file ", path, " remove OK!")
   273  	}
   274  	return nil
   275  }
   276  
   277  // BeforeApprove is a no-op to ensure structs which embed Item implement Hookable
   278  func (i Item) BeforeApprove(res http.ResponseWriter, req *http.Request) error {
   279  	return nil
   280  }
   281  
   282  // AfterApprove is a no-op to ensure structs which embed Item implement Hookable
   283  func (i Item) AfterApprove(res http.ResponseWriter, req *http.Request) error {
   284  	return nil
   285  }
   286  
   287  // BeforeReject is a no-op to ensure structs which embed Item implement Hookable
   288  func (i Item) BeforeReject(res http.ResponseWriter, req *http.Request) error {
   289  	return nil
   290  }
   291  
   292  // AfterReject is a no-op to ensure structs which embed Item implement Hookable
   293  func (i Item) AfterReject(res http.ResponseWriter, req *http.Request) error {
   294  	return nil
   295  }
   296  
   297  // BeforeEnable is a no-op to ensure structs which embed Item implement Hookable
   298  func (i Item) BeforeEnable(res http.ResponseWriter, req *http.Request) error {
   299  	return nil
   300  }
   301  
   302  // AfterEnable is a no-op to ensure structs which embed Item implement Hookable
   303  func (i Item) AfterEnable(res http.ResponseWriter, req *http.Request) error {
   304  	return nil
   305  }
   306  
   307  // BeforeDisable is a no-op to ensure structs which embed Item implement Hookable
   308  func (i Item) BeforeDisable(res http.ResponseWriter, req *http.Request) error {
   309  	return nil
   310  }
   311  
   312  // AfterDisable is a no-op to ensure structs which embed Item implement Hookable
   313  func (i Item) AfterDisable(res http.ResponseWriter, req *http.Request) error {
   314  	return nil
   315  }
   316  
   317  // SearchMapping returns a default implementation of a Bleve IndexMappingImpl
   318  // partially implements search.Searchable
   319  func (i Item) SearchMapping() (*mapping.IndexMappingImpl, error) {
   320  	mapping := bleve.NewIndexMapping()
   321  	mapping.StoreDynamic = false
   322  
   323  	return mapping, nil
   324  }
   325  
   326  // IndexContent determines if a type should be indexed for searching
   327  // partially implements search.Searchable
   328  func (i Item) IndexContent() bool {
   329  	return false
   330  }
   331  
   332  // Slug returns a URL friendly string from the title of a post item
   333  func Slug(i Identifiable) (string, error) {
   334  	// get the name of the post item
   335  	name := strings.TrimSpace(i.String())
   336  
   337  	// filter out non-alphanumeric character or non-whitespace
   338  	slug, err := stringToSlug(name)
   339  	if err != nil {
   340  		return "", err
   341  	}
   342  
   343  	return slug, nil
   344  }
   345  
   346  func isMn(r rune) bool {
   347  	return unicode.Is(unicode.Mn, r) // Mn: nonspacing marks
   348  }
   349  
   350  // modified version of: https://www.socketloop.com/tutorials/golang-format-strings-to-seo-friendly-url-example
   351  func stringToSlug(s string) (string, error) {
   352  	src := []byte(strings.ToLower(s))
   353  
   354  	// Range over compiled regex and replacements from init().
   355  	for rx := range rxList {
   356  		src = rx.ReplaceAll(src, rxList[rx])
   357  	}
   358  
   359  	str := strings.Replace(string(src), "'", "", -1)
   360  	str = strings.Replace(str, `"`, "", -1)
   361  	str = strings.Replace(str, "&", "-", -1)
   362  
   363  	t := transform.Chain(norm.NFD, transform.RemoveFunc(isMn), norm.NFC)
   364  	slug, _, err := transform.String(t, str)
   365  	if err != nil {
   366  		return "", err
   367  	}
   368  
   369  	return strings.TrimSpace(slug), nil
   370  }
   371  
   372  // NormalizeString removes and replaces illegal characters for URLs and other
   373  // path entities. Useful for taking user input and converting it for keys or URLs.
   374  func NormalizeString(s string) (string, error) {
   375  	return stringToSlug(s)
   376  }