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 }