diff options
-rw-r--r-- | cmd/ponzu/cli_test.go | 65 | ||||
-rw-r--r-- | cmd/ponzu/contentType.tmpl | 51 | ||||
-rw-r--r-- | cmd/ponzu/generate.go | 189 | ||||
-rw-r--r-- | cmd/ponzu/main.go | 5 | ||||
-rw-r--r-- | cmd/ponzu/options.go | 156 | ||||
-rw-r--r-- | cmd/ponzu/vendor/github.com/boltdb/bolt/bolt_windows.go | 2 | ||||
-rw-r--r-- | cmd/ponzu/vendor/github.com/satori/go.uuid/.travis.yml | 21 | ||||
-rw-r--r-- | cmd/ponzu/vendor/github.com/satori/go.uuid/LICENSE | 20 | ||||
-rw-r--r-- | cmd/ponzu/vendor/github.com/satori/go.uuid/README.md | 65 | ||||
-rw-r--r-- | cmd/ponzu/vendor/github.com/satori/go.uuid/benchmarks_test.go | 123 | ||||
-rw-r--r-- | cmd/ponzu/vendor/github.com/satori/go.uuid/uuid.go | 481 | ||||
-rw-r--r-- | cmd/ponzu/vendor/github.com/satori/go.uuid/uuid_test.go | 633 | ||||
-rw-r--r-- | content/item.go | 40 | ||||
-rw-r--r-- | content/post.go | 83 | ||||
-rw-r--r-- | management/editor/editor.go | 1 | ||||
-rw-r--r-- | management/manager/manager.go | 11 | ||||
-rw-r--r-- | system/admin/filesystem.go | 21 | ||||
-rw-r--r-- | system/admin/handlers.go | 14 | ||||
-rw-r--r-- | system/api/external.go | 2 | ||||
-rw-r--r-- | system/db/content.go | 5 |
20 files changed, 1717 insertions, 271 deletions
diff --git a/cmd/ponzu/cli_test.go b/cmd/ponzu/cli_test.go new file mode 100644 index 0000000..76feac3 --- /dev/null +++ b/cmd/ponzu/cli_test.go @@ -0,0 +1,65 @@ +package main + +import "testing" + +func TestParseType(t *testing.T) { + // blog title:string Author:string PostCategory:string content:string some_thing:int + args := []string{ + "blog", "title:string", "Author:string", + "PostCategory:string", "content:string", + "some_thing:int", "Some_otherThing:float64", + } + + gt, err := parseType(args) + if err != nil { + t.Errorf("Failed: %s", err.Error()) + } + + if gt.Name != "Blog" { + t.Errorf("Expected %s, got: %s", "Blog", gt.Name) + } +} + +func TestFieldJSONName(t *testing.T) { + cases := map[string]string{ + "_T": "t", + "T": "t", + "_tT_": "t_t_", + "TestCapsNoSym": "test_caps_no_sym", + "test_Some_caps_Sym": "test_some_caps_sym", + "testnocaps": "testnocaps", + "_Test_Caps_Sym_odd": "test_caps_sym_odd", + "test-hyphen": "test-hyphen", + "Test-hyphen-Caps": "test-hyphen-caps", + "Test-Hyphen_Sym-Caps": "test-hyphen_sym-caps", + } + + for input, expected := range cases { + output := fieldJSONName(input) + if output != expected { + t.Errorf("Expected: %s, got: %s", expected, output) + } + } +} + +func TestFieldName(t *testing.T) { + cases := map[string]string{ + "_T": "T", + "T": "T", + "_tT_": "TT", + "TestCapsNoSym": "TestCapsNoSym", + "test_Some_caps_Sym": "TestSomeCapsSym", + "testnocaps": "Testnocaps", + "_Test_Caps_Sym_odd": "TestCapsSymOdd", + "test-hyphen": "TestHyphen", + "Test-hyphen-Caps": "TestHyphenCaps", + "Test-Hyphen_Sym-Caps": "TestHyphenSymCaps", + } + + for input, expected := range cases { + output := fieldName(input) + if output != expected { + t.Errorf("Expected: %s, got: %s", expected, output) + } + } +} diff --git a/cmd/ponzu/contentType.tmpl b/cmd/ponzu/contentType.tmpl new file mode 100644 index 0000000..df582a0 --- /dev/null +++ b/cmd/ponzu/contentType.tmpl @@ -0,0 +1,51 @@ +package content + +import ( + "fmt" + + "github.com/bosssauce/ponzu/management/editor" +) + +type {{ .Name }} struct { + Item + editor editor.Editor + + {{ range .Fields }}{{ .Name }} {{ .TypeName }} `json:"{{ .JSONName }}"` + {{ end }} +} + +// MarshalEditor writes a buffer of html to edit a {{ .Name }} +// partially implements editor.Editable +func ({{ .Initial }} *{{ .Name }}) MarshalEditor() ([]byte, error) { + view, err := editor.Form({{ .Initial }}, + // Take note that the first argument to these Input-like functions + // is the string version of each {{ .Name }} field, and must follow + // this pattern for auto-decoding and auto-encoding reasons:{{ $initial := .Initial }} + {{ range .Fields }}editor.Field{ + View: editor.Input("{{ .Name }}", {{ $initial }}, map[string]string{ + "label": "{{ .Name }}", + "type": "text", + "placeholder": "Enter the {{ .Name }} here", + }), + },{{ end }} + ) + + if err != nil { + return nil, fmt.Errorf("Failed to render {{ .Name }} editor view: %s", err.Error()) + } + + return view, nil +} + +func init() { + Types["{{ .Name }}"] = func() interface{} { return new({{ .Name }}) } +} + +// ContentName is required to set the display name for a piece of content in the editor +// Partially implements editor.Editable +func ({{ .Initial }} *{{ .Name }}) ContentName() string { return fmt.Sprintf("{{ .Name }} - ID: %s", {{ .Initial }}.UniqueID()) } + +// Editor is a buffer of bytes for the Form function to write input views +// partially implements editor.Editable +func ({{ .Initial }} *{{ .Name }}) Editor() *editor.Editor { return &{{ .Initial }}.editor } + diff --git a/cmd/ponzu/generate.go b/cmd/ponzu/generate.go new file mode 100644 index 0000000..e98e3fe --- /dev/null +++ b/cmd/ponzu/generate.go @@ -0,0 +1,189 @@ +package main + +import ( + "bytes" + "fmt" + "go/format" + "os" + "path/filepath" + "strings" + "text/template" +) + +type generateType struct { + Name string + Initial string + Fields []generateField +} + +type generateField struct { + Name string + TypeName string + JSONName string +} + +// blog title:string Author:string PostCategory:string content:string some_thing:int +func parseType(args []string) (generateType, error) { + t := generateType{ + Name: fieldName(args[0]), + } + t.Initial = strings.ToLower(string(t.Name[0])) + + fields := args[1:] + for _, field := range fields { + f, err := parseField(field) + if err != nil { + return generateType{}, err + } + + t.Fields = append(t.Fields, f) + } + + return t, nil +} + +func parseField(raw string) (generateField, error) { + // title:string + if !strings.Contains(raw, ":") { + return generateField{}, fmt.Errorf("Invalid generate argument. [%s]", raw) + } + + pair := strings.Split(raw, ":") + field := generateField{ + Name: fieldName(pair[0]), + TypeName: strings.ToLower(pair[1]), + JSONName: fieldJSONName(pair[0]), + } + + return field, nil +} + +// get the initial field name passed and check it for all possible cases +// MyTitle:string myTitle:string my_title:string -> MyTitle +// error-message:string -> ErrorMessage +func fieldName(name string) string { + // remove _ or - if first character + if name[0] == '-' || name[0] == '_' { + name = name[1:] + } + + // remove _ or - if last character + if name[len(name)-1] == '-' || name[len(name)-1] == '_' { + name = name[:len(name)-1] + } + + // upcase the first character + name = strings.ToUpper(string(name[0])) + name[1:] + + // remove _ or - character, and upcase the character immediately following + for i := 0; i < len(name); i++ { + r := rune(name[i]) + if isUnderscore(r) || isHyphen(r) { + up := strings.ToUpper(string(name[i+1])) + name = name[:i] + up + name[i+2:] + } + } + + return name +} + +// get the initial field name passed and convert to json-like name +// MyTitle:string myTitle:string my_title:string -> my_title +// error-message:string -> error-message +func fieldJSONName(name string) string { + // remove _ or - if first character + if name[0] == '-' || name[0] == '_' { + name = name[1:] + } + + // downcase the first character + name = strings.ToLower(string(name[0])) + name[1:] + + // check for uppercase character, downcase and insert _ before it if i-1 + // isn't already _ or - + for i := 0; i < len(name); i++ { + r := rune(name[i]) + if isUpper(r) { + low := strings.ToLower(string(r)) + if name[i-1] == '_' || name[i-1] == '-' { + name = name[:i] + low + name[i+1:] + } else { + name = name[:i] + "_" + low + name[i+1:] + } + } + } + + return name +} + +func isUpper(char rune) bool { + if char >= 'A' && char <= 'Z' { + return true + } + + return false +} + +func isUnderscore(char rune) bool { + return char == '_' +} + +func isHyphen(char rune) bool { + return char == '-' +} + +func generateContentType(args []string) error { + name := args[0] + fileName := strings.ToLower(name) + ".go" + + // open file in ./content/ dir + // if exists, alert user of conflict + pwd, err := os.Getwd() + if err != nil { + return err + } + + contentDir := filepath.Join(pwd, "content") + filePath := filepath.Join(contentDir, fileName) + + if _, err := os.Stat(filePath); !os.IsNotExist(err) { + return fmt.Errorf("Please remove '%s' before executing this command.", fileName) + } + + // no file exists.. ok to write new one + file, err := os.Create(filePath) + defer file.Close() + if err != nil { + return err + } + + // parse type info from args + gt, err := parseType(args) + if err != nil { + return fmt.Errorf("Failed to parse type args: %s", err.Error()) + } + + tmplPath := filepath.Join(pwd, "cmd", "ponzu", "contentType.tmpl") + tmpl, err := template.ParseFiles(tmplPath) + if err != nil { + return fmt.Errorf("Failed to parse template: %s", err.Error()) + } + + buf := &bytes.Buffer{} + err = tmpl.Execute(buf, gt) + if err != nil { + return fmt.Errorf("Failed to execute template: %s", err.Error()) + } + + fmtBuf, err := format.Source(buf.Bytes()) + if err != nil { + return fmt.Errorf("Failed to format template: %s", err.Error()) + } + + _, err = file.Write(fmtBuf) + if err != nil { + return fmt.Errorf("Failed to write generated file buffer: %s", err.Error()) + } + + return nil +} diff --git a/cmd/ponzu/main.go b/cmd/ponzu/main.go index 234d3b6..2a60507 100644 --- a/cmd/ponzu/main.go +++ b/cmd/ponzu/main.go @@ -120,11 +120,12 @@ func main() { os.Exit(0) } - err := generateContentType(args[1], "") + err := generateContentType(args[1:]) if err != nil { fmt.Println(err) os.Exit(1) } + case "build": err := buildPonzuServer(args) if err != nil { @@ -197,8 +198,10 @@ func main() { } log.Fatal(http.ListenAndServe(fmt.Sprintf(":%d", port), nil)) + case "": flag.PrintDefaults() + default: flag.PrintDefaults() } diff --git a/cmd/ponzu/options.go b/cmd/ponzu/options.go index 244a759..7c93130 100644 --- a/cmd/ponzu/options.go +++ b/cmd/ponzu/options.go @@ -3,7 +3,6 @@ package main import ( "errors" "fmt" - "html/template" "io" "io/ioutil" "os" @@ -12,143 +11,6 @@ import ( "strings" ) -func generateContentType(name, path string) error { - fileName := strings.ToLower(name) + ".go" - typeName := strings.ToUpper(string(name[0])) + string(name[1:]) - - // contain processed name an info for template - data := map[string]string{ - "name": typeName, - "initial": string(fileName[0]), - } - - // open file in ./content/ dir - // if exists, alert user of conflict - pwd, err := os.Getwd() - if err != nil { - return err - } - - if path != "" { - pwd = path - } - - contentDir := filepath.Join(pwd, "content") - filePath := filepath.Join(contentDir, fileName) - - if _, err := os.Stat(filePath); !os.IsNotExist(err) { - return fmt.Errorf("Please remove '%s' before executing this command.", fileName) - } - - // no file exists.. ok to write new one - file, err := os.Create(filePath) - defer file.Close() - if err != nil { - return err - } - - // execute template - tmpl := template.Must(template.New("content").Parse(contentTypeTmpl)) - err = tmpl.Execute(file, data) - if err != nil { - return err - } - - return nil -} - -const contentTypeTmpl = ` -package content - -import ( - "fmt" - - "github.com/bosssauce/ponzu/management/editor" -) - -// {{ .name }} is the generic content struct -type {{ .name }} struct { - Item - editor editor.Editor - - // required: all maintained {{ .name }} fields must have json tags! - Title string ` + "`json:" + `"title"` + "`" + ` - Content string ` + "`json:" + `"content"` + "`" + ` - Author string ` + "`json:" + `"author"` + "`" + ` - Photo string ` + "`json:" + `"photo"` + "`" + ` - Category []string ` + "`json:" + `"category"` + "`" + ` - Theme string ` + "`json:" + `"theme"` + "`" + ` -} - -// MarshalEditor writes a buffer of html to edit a {{ .name }} -// partially implements editor.Editable -func ({{ .initial }} *{{ .name }}) MarshalEditor() ([]byte, error) { - view, err := editor.Form({{ .initial }}, - editor.Field{ - // Take note that the first argument to these Input-like methods - // is the string version of each {{ .name }} field, and must follow - // this pattern for auto-decoding and auto-encoding reasons. - View: editor.Input("Title", {{ .initial }}, map[string]string{ - "label": "{{ .name }} Title", - "type": "text", - "placeholder": "Enter your {{ .name }} Title here", - }), - }, - editor.Field{ - View: editor.Richtext("Content", {{ .initial }}, map[string]string{ - "label": "Content", - "placeholder": "Add the content of your {{ .name }} here", - }), - }, - editor.Field{ - View: editor.Input("Author", {{ .initial }}, map[string]string{ - "label": "Author", - "type": "text", - "placeholder": "Enter the author name here", - }), - }, - editor.Field{ - View: editor.File("Photo", {{ .initial }}, map[string]string{ - "label": "Author Photo", - "placeholder": "Upload a profile picture for the author", - }), - }, - editor.Field{ - View: editor.Tags("Category", {{ .initial }}, map[string]string{ - "label": "{{ .name }} Category", - }), - }, - editor.Field{ - View: editor.Select("Theme", {{ .initial }}, map[string]string{ - "label": "Theme Style", - }, map[string]string{ - "dark": "Dark", - "light": "Light", - }), - }, - ) - - if err != nil { - return nil, fmt.Errorf("Failed to render {{ .name }} editor view: %s", err.Error()) - } - - return view, nil -} - -func init() { - Types["{{ .name }}"] = func() interface{} { return new({{ .name }}) } -} - -// ContentName is required to set the display name for a piece of content in the editor -// Partially implements editor.Editable -func ({{ .initial }} *{{ .name }}) ContentName() string { return {{ .initial }}.Title } - -// Editor is a buffer of bytes for the Form function to write input views -// partially implements editor.Editable -func ({{ .initial }} *{{ .name }}) Editor() *editor.Editor { return &{{ .initial }}.editor } - -` - func newProjectInDir(path string) error { // set path to be nested inside $GOPATH/src gopath := os.Getenv("GOPATH") @@ -230,12 +92,6 @@ func createProjInDir(path string) error { return err } - err = generateContentType("post", path) - if err != nil { - // TODO: rollback, remove ponzu project from path - return err - } - fmt.Println("Dev build cloned from " + local + ":ponzu-dev") return nil } @@ -278,12 +134,6 @@ func createProjInDir(path string) error { return err } - err = generateContentType("post", path) - if err != nil { - // TODO: rollback, remove ponzu project from path - return err - } - gitDir := filepath.Join(path, ".git") err = os.RemoveAll(gitDir) if err != nil { @@ -311,8 +161,7 @@ func vendorCorePackages(path string) error { } } - // create a user 'content' package, and give it a single 'post.go' file - // using generateContentType("post") + // create a user 'content' package contentPath := filepath.Join(path, "content") err = os.Mkdir(contentPath, os.ModeDir|os.ModePerm) if err != nil { @@ -382,7 +231,8 @@ func buildPonzuServer(args []string) error { // execute go build -o ponzu-cms cmd/ponzu/*.go mainPath := filepath.Join(pwd, "cmd", "ponzu", "main.go") optsPath := filepath.Join(pwd, "cmd", "ponzu", "options.go") - build := exec.Command("go", "build", "-o", "ponzu-server", mainPath, optsPath) + genPath := filepath.Join(pwd, "cmd", "ponzu", "generate.go") + build := exec.Command("go", "build", "-o", "ponzu-server", mainPath, optsPath, genPath) build.Stderr = os.Stderr build.Stdout = os.Stdout diff --git a/cmd/ponzu/vendor/github.com/boltdb/bolt/bolt_windows.go b/cmd/ponzu/vendor/github.com/boltdb/bolt/bolt_windows.go index d538e6a..b00fb07 100644 --- a/cmd/ponzu/vendor/github.com/boltdb/bolt/bolt_windows.go +++ b/cmd/ponzu/vendor/github.com/boltdb/bolt/bolt_windows.go @@ -89,7 +89,7 @@ func flock(db *DB, mode os.FileMode, exclusive bool, timeout time.Duration) erro func funlock(db *DB) error { err := unlockFileEx(syscall.Handle(db.lockfile.Fd()), 0, 1, 0, &syscall.Overlapped{}) db.lockfile.Close() - os.Remove(db.path+lockExt) + os.Remove(db.path + lockExt) return err } diff --git a/cmd/ponzu/vendor/github.com/satori/go.uuid/.travis.yml b/cmd/ponzu/vendor/github.com/satori/go.uuid/.travis.yml new file mode 100644 index 0000000..bf90ad5 --- /dev/null +++ b/cmd/ponzu/vendor/github.com/satori/go.uuid/.travis.yml @@ -0,0 +1,21 @@ +language: go +sudo: false +go: + - 1.2 + - 1.3 + - 1.4 + - 1.5 + - 1.6 + - 1.7 + - tip +matrix: + allow_failures: + - go: tip + fast_finish: true +before_install: + - go get github.com/mattn/goveralls + - go get golang.org/x/tools/cmd/cover +script: + - $HOME/gopath/bin/goveralls -service=travis-ci +notifications: + email: false diff --git a/cmd/ponzu/vendor/github.com/satori/go.uuid/LICENSE b/cmd/ponzu/vendor/github.com/satori/go.uuid/LICENSE new file mode 100644 index 0000000..488357b --- /dev/null +++ b/cmd/ponzu/vendor/github.com/satori/go.uuid/LICENSE @@ -0,0 +1,20 @@ +Copyright (C) 2013-2016 by Maxim Bublis <b@codemonkey.ru> + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/cmd/ponzu/vendor/github.com/satori/go.uuid/README.md b/cmd/ponzu/vendor/github.com/satori/go.uuid/README.md new file mode 100644 index 0000000..b6aad1c --- /dev/null +++ b/cmd/ponzu/vendor/github.com/satori/go.uuid/README.md @@ -0,0 +1,65 @@ +# UUID package for Go language + +[](https://travis-ci.org/satori/go.uuid) +[](https://coveralls.io/github/satori/go.uuid) +[](http://godoc.org/github.com/satori/go.uuid) + +This package provides pure Go implementation of Universally Unique Identifier (UUID). Supported both creation and parsing of UUIDs. + +With 100% test coverage and benchmarks out of box. + +Supported versions: +* Version 1, based on timestamp and MAC address (RFC 4122) +* Version 2, based on timestamp, MAC address and POSIX UID/GID (DCE 1.1) +* Version 3, based on MD5 hashing (RFC 4122) +* Version 4, based on random numbers (RFC 4122) +* Version 5, based on SHA-1 hashing (RFC 4122) + +## Installation + +Use the `go` command: + + $ go get github.com/satori/go.uuid + +## Requirements + +UUID package requires Go >= 1.2. + +## Example + +```go +package main + +import ( + "fmt" + "github.com/satori/go.uuid" +) + +func main() { + // Creating UUID Version 4 + u1 := uuid.NewV4() + fmt.Printf("UUIDv4: %s\n", u1) + + // Parsing UUID from string input + u2, err := uuid.FromString("6ba7b810-9dad-11d1-80b4-00c04fd430c8") + if err != nil { + fmt.Printf("Something gone wrong: %s", err) + } + fmt.Printf("Successfully parsed: %s", u2) +} +``` + +## Documentation + +[Documentation](http://godoc.org/github.com/satori/go.uuid) is hosted at GoDoc project. + +## Links +* [RFC 4122](http://tools.ietf.org/html/rfc4122) +* [DCE 1.1: Authentication and Security Services](http://pubs.opengroup.org/onlinepubs/9696989899/chap5.htm#tagcjh_08_02_01_01) + +## Copyright + +Copyright (C) 2013-2016 by Maxim Bublis <b@codemonkey.ru>. + +UUID package released under MIT License. +See [LICENSE](https://github.com/satori/go.uuid/blob/master/LICENSE) for details. diff --git a/cmd/ponzu/vendor/github.com/satori/go.uuid/benchmarks_test.go b/cmd/ponzu/vendor/github.com/satori/go.uuid/benchmarks_test.go new file mode 100644 index 0000000..c3baeab --- /dev/null +++ b/cmd/ponzu/vendor/github.com/satori/go.uuid/benchmarks_test.go @@ -0,0 +1,123 @@ +// Copyright (C) 2013-2015 by Maxim Bublis <b@codemonkey.ru> +// +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +package uuid + +import ( + "testing" +) + +func BenchmarkFromBytes(b *testing.B) { + bytes := []byte{0x6b, 0xa7, 0xb8, 0x10, 0x9d, 0xad, 0x11, 0xd1, 0x80, 0xb4, 0x00, 0xc0, 0x4f, 0xd4, 0x30, 0xc8} + for i := 0; i < b.N; i++ { + FromBytes(bytes) + } +} + +func BenchmarkFromString(b *testing.B) { + s := "6ba7b810-9dad-11d1-80b4-00c04fd430c8" + for i := 0; i < b.N; i++ { + FromString(s) + } +} + +func BenchmarkFromStringUrn(b *testing.B) { + s := "urn:uuid:6ba7b810-9dad-11d1-80b4-00c04fd430c8" + for i := 0; i < b.N; i++ { + FromString(s) + } +} + +func BenchmarkFromStringWithBrackets(b *testing.B) { + s := "{6ba7b810-9dad-11d1-80b4-00c04fd430c8}" + for i := 0; i < b.N; i++ { + FromString(s) + } +} + +func BenchmarkNewV1(b *testing.B) { + for i := 0; i < b.N; i++ { + NewV1() + } +} + +func BenchmarkNewV2(b *testing.B) { + for i := 0; i < b.N; i++ { + NewV2(DomainPerson) + } +} + +func BenchmarkNewV3(b *testing.B) { + for i := 0; i < b.N; i++ { + NewV3(NamespaceDNS, "www.example.com") + } +} + +func BenchmarkNewV4(b *testing.B) { + for i := 0; i < b.N; i++ { + NewV4() + } +} + +func BenchmarkNewV5(b *testing.B) { + for i := 0; i < b.N; i++ { + NewV5(NamespaceDNS, "www.example.com") + } +} + +func BenchmarkMarshalBinary(b *testing.B) { + u := NewV4() + for i := 0; i < b.N; i++ { + u.MarshalBinary() + } +} + +func BenchmarkMarshalText(b *testing.B) { + u := NewV4() + for i := 0; i < b.N; i++ { + u.MarshalText() + } +} + +func BenchmarkUnmarshalBinary(b *testing.B) { + bytes := []byte{0x6b, 0xa7, 0xb8, 0x10, 0x9d, 0xad, 0x11, 0xd1, 0x80, 0xb4, 0x00, 0xc0, 0x4f, 0xd4, 0x30, 0xc8} + u := UUID{} + for i := 0; i < b.N; i++ { + u.UnmarshalBinary(bytes) + } +} + +func BenchmarkUnmarshalText(b *testing.B) { + bytes := []byte("6ba7b810-9dad-11d1-80b4-00c04fd430c8") + u := UUID{} + for i := 0; i < b.N; i++ { + u.UnmarshalText(bytes) + } +} + +var sink string + +func BenchmarkMarshalToString(b *testing.B) { + u := NewV4() + for i := 0; i < b.N; i++ { + sink = u.String() + } +} diff --git a/cmd/ponzu/vendor/github.com/satori/go.uuid/uuid.go b/cmd/ponzu/vendor/github.com/satori/go.uuid/uuid.go new file mode 100644 index 0000000..295f3fc --- /dev/null +++ b/cmd/ponzu/vendor/github.com/satori/go.uuid/uuid.go @@ -0,0 +1,481 @@ +// Copyright (C) 2013-2015 by Maxim Bublis <b@codemonkey.ru> +// +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +// Package uuid provides implementation of Universally Unique Identifier (UUID). +// Supported versions are 1, 3, 4 and 5 (as specified in RFC 4122) and +// version 2 (as specified in DCE 1.1). +package uuid + +import ( + "bytes" + "crypto/md5" + "crypto/rand" + "crypto/sha1" + "database/sql/driver" + "encoding/binary" + "encoding/hex" + "fmt" + "hash" + "net" + "os" + "sync" + "time" +) + +// UUID layout variants. +const ( + VariantNCS = iota + VariantRFC4122 + VariantMicrosoft + VariantFuture +) + +// UUID DCE domains. +const ( + DomainPerson = iota + DomainGroup + DomainOrg +) + +// Difference in 100-nanosecond intervals between +// UUID epoch (October 15, 1582) and Unix epoch (January 1, 1970). +const epochStart = 122192928000000000 + +// Used in string method conversion +const dash byte = '-' + +// UUID v1/v2 storage. +var ( + storageMutex sync.Mutex + storageOnce sync.Once + epochFunc = unixTimeFunc + clockSequence uint16 + lastTime uint64 + hardwareAddr [6]byte + posixUID = uint32(os.Getuid()) + posixGID = uint32(os.Getgid()) +) + +// String parse helpers. +var ( + urnPrefix = []byte("urn:uuid:") + byteGroups = []int{8, 4, 4, 4, 12} +) + +func initClockSequence() { + buf := make([]byte, 2) + safeRandom(buf) + clockSequence = binary.BigEndian.Uint16(buf) +} + +func initHardwareAddr() { + interfaces, err := net.Interfaces() + if err == nil { + for _, iface := range interfaces { + if len(iface.HardwareAddr) >= 6 { + copy(hardwareAddr[:], iface.HardwareAddr) + return + } + } + } + + // Initialize hardwareAddr randomly in case + // of real network interfaces absence + safeRandom(hardwareAddr[:]) + + // Set multicast bit as recommended in RFC 4122 + hardwareAddr[0] |= 0x01 +} + +func initStorage() { + initClockSequence() + initHardwareAddr() +} + +func safeRandom(dest []byte) { + if _, err := rand.Read(dest); err != nil { + panic(err) + } +} + +// Returns difference in 100-nanosecond intervals between +// UUID epoch (October 15, 1582) and current time. +// This is default epoch calculation function. +func unixTimeFunc() uint64 { + return epochStart + uint64(time.Now().UnixNano()/100) +} + +// UUID representation compliant with specification +// described in RFC 4122. +type UUID [16]byte + +// NullUUID can be used with the standard sql package to represent a +// UUID value that can be NULL in the database +type NullUUID struct { + UUID UUID + Valid bool +} + +// The nil UUID is special form of UUID that is specified to have all +// 128 bits set to zero. +var Nil = UUID{} + +// Predefined namespace UUIDs. +var ( + NamespaceDNS, _ = FromString("6ba7b810-9dad-11d1-80b4-00c04fd430c8") + NamespaceURL, _ = FromString("6ba7b811-9dad-11d1-80b4-00c04fd430c8") + NamespaceOID, _ = FromString("6ba7b812-9dad-11d1-80b4-00c04fd430c8") + NamespaceX500, _ = FromString("6ba7b814-9dad-11d1-80b4-00c04fd430c8") +) + +// And returns result of binary AND of two UUIDs. +func And(u1 UUID, u2 UUID) UUID { + u := UUID{} + for i := 0; i < 16; i++ { + u[i] = u1[i] & u2[i] + } + return u +} + +// Or returns result of binary OR of two UUIDs. +func Or(u1 UUID, u2 UUID) UUID { + u := UUID{} + for i := 0; i < 16; i++ { + u[i] = u1[i] | u2[i] + } + return u +} + +// Equal returns true if u1 and u2 equals, otherwise returns false. +func Equal(u1 UUID, u2 UUID) bool { + return bytes.Equal(u1[:], u2[:]) +} + +// Version returns algorithm version used to generate UUID. +func (u UUID) Version() uint { + return uint(u[6] >> 4) +} + +// Variant returns UUID layout variant. +func (u UUID) Variant() uint { + switch { + case (u[8] & 0x80) == 0x00: + return VariantNCS + case (u[8]&0xc0)|0x80 == 0x80: + return VariantRFC4122 + case (u[8]&0xe0)|0xc0 == 0xc0: + return VariantMicrosoft + } + return VariantFuture +} + +// Bytes returns bytes slice representation of UUID. +func (u UUID) Bytes() []byte { + return u[:] +} + +// Returns canonical string representation of UUID: +// xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx. +func (u UUID) String() string { + buf := make([]byte, 36) + + hex.Encode(buf[0:8], u[0:4]) + buf[8] = dash + hex.Encode(buf[9:13], u[4:6]) + buf[13] = dash + hex.Encode(buf[14:18], u[6:8]) + buf[18] = dash + hex.Encode(buf[19:23], u[8:10]) + buf[23] = dash + hex.Encode(buf[24:], u[10:]) + + return string(buf) +} + +// SetVersion sets version bits. +func (u *UUID) SetVersion(v byte) { + u[6] = (u[6] & 0x0f) | (v << 4) +} + +// SetVariant sets variant bits as described in RFC 4122. +func (u *UUID) SetVariant() { + u[8] = (u[8] & 0xbf) | 0x80 +} + +// MarshalText implements the encoding.TextMarshaler interface. +// The encoding is the same as returned by String. +func (u UUID) MarshalText() (text []byte, err error) { + text = []byte(u.String()) + return +} + +// UnmarshalText implements the encoding.TextUnmarshaler interface. +// Following formats are supported: +// "6ba7b810-9dad-11d1-80b4-00c04fd430c8", +// "{6ba7b810-9dad-11d1-80b4-00c04fd430c8}", +// "urn:uuid:6ba7b810-9dad-11d1-80b4-00c04fd430c8" +func (u *UUID) UnmarshalText(text []byte) (err error) { + if len(text) < 32 { + err = fmt.Errorf("uuid: UUID string too short: %s", text) + return + } + + t := text[:] + braced := false + + if bytes.Equal(t[:9], urnPrefix) { + t = t[9:] + } else if t[0] == '{' { + braced = true + t = t[1:] + } + + b := u[:] + + for i, byteGroup := range byteGroups { + if i > 0 { + if t[0] != '-' { + err = fmt.Errorf("uuid: invalid string format") + return + } + t = t[1:] + } + + if len(t) < byteGroup { + err = fmt.Errorf("uuid: UUID string too short: %s", text) + return + } + + if i == 4 && len(t) > byteGroup && + ((braced && t[byteGroup] != '}') || len(t[byteGroup:]) > 1 || !braced) { + err = fmt.Errorf("uuid: UUID string too long: %s", text) + return + } + + _, err = hex.Decode(b[:byteGroup/2], t[:byteGroup]) + if err != nil { + return + } + + t = t[byteGroup:] + b = b[byteGroup/2:] + } + + return +} + +// MarshalBinary implements the encoding.BinaryMarshaler interface. +func (u UUID) MarshalBinary() (data []byte, err error) { + data = u.Bytes() + return +} + +// UnmarshalBinary implements the encoding.BinaryUnmarshaler interface. +// It will return error if the slice isn't 16 bytes long. +func (u *UUID) UnmarshalBinary(data []byte) (err error) { + if len(data) != 16 { + err = fmt.Errorf("uuid: UUID must be exactly 16 bytes long, got %d bytes", len(data)) + return + } + copy(u[:], data) + + return +} + +// Value implements the driver.Valuer interface. +func (u UUID) Value() (driver.Value, error) { + return u.String(), nil +} + +// Scan implements the sql.Scanner interface. +// A 16-byte slice is handled by UnmarshalBinary, while +// a longer byte slice or a string is handled by UnmarshalText. +func (u *UUID) Scan(src interface{}) error { + switch src := src.(type) { + case []byte: + if len(src) == 16 { + return u.UnmarshalBinary(src) + } + return u.UnmarshalText(src) + + case string: + return u.UnmarshalText([]byte(src)) + } + + return fmt.Errorf("uuid: cannot convert %T to UUID", src) +} + +// Value implements the driver.Valuer interface. +func (u NullUUID) Value() (driver.Value, error) { + if !u.Valid { + return nil, nil + } + // Delegate to UUID Value function + return u.UUID.Value() +} + +// Scan implements the sql.Scanner interface. +func (u *NullUUID) Scan(src interface{}) error { + if src == nil { + u.UUID, u.Valid = Nil, false + return nil + } + + // Delegate to UUID Scan function + u.Valid = true + return u.UUID.Scan(src) +} + +// FromBytes returns UUID converted from raw byte slice input. +// It will return error if the slice isn't 16 bytes long. +func FromBytes(input []byte) (u UUID, err error) { + err = u.UnmarshalBinary(input) + return +} + +// FromBytesOrNil returns UUID converted from raw byte slice input. +// Same behavior as FromBytes, but returns a Nil UUID on error. +func FromBytesOrNil(input []byte) UUID { + uuid, err := FromBytes(input) + if err != nil { + return Nil + } + return uuid +} + +// FromString returns UUID parsed from string input. +// Input is expected in a form accepted by UnmarshalText. +func FromString(input string) (u UUID, err error) { + err = u.UnmarshalText([]byte(input)) + return +} + +// FromStringOrNil returns UUID parsed from string input. +// Same behavior as FromString, but returns a Nil UUID on error. +func FromStringOrNil(input string) UUID { + uuid, err := FromString(input) + if err != nil { + return Nil + } + return uuid +} + +// Returns UUID v1/v2 storage state. +// Returns epoch timestamp, clock sequence, and hardware address. +func getStorage() (uint64, uint16, []byte) { + storageOnce.Do(initStorage) + + storageMutex.Lock() + defer storageMutex.Unlock() + + timeNow := epochFunc() + // Clock changed backwards since last UUID generation. + // Should increase clock sequence. + if timeNow <= lastTime { + clockSequence++ + } + lastTime = timeNow + + return timeNow, clockSequence, hardwareAddr[:] +} + +// NewV1 returns UUID based on current timestamp and MAC address. +func NewV1() UUID { + u := UUID{} + + timeNow, clockSeq, hardwareAddr := getStorage() + + binary.BigEndian.PutUint32(u[0:], uint32(timeNow)) + binary.BigEndian.PutUint16(u[4:], uint16(timeNow>>32)) + binary.BigEndian.PutUint16(u[6:], uint16(timeNow>>48)) + binary.BigEndian.PutUint16(u[8:], clockSeq) + + copy(u[10:], hardwareAddr) + + u.SetVersion(1) + u.SetVariant() + + return u +} + +// NewV2 returns DCE Security UUID based on POSIX UID/GID. +func NewV2(domain byte) UUID { + u := UUID{} + + timeNow, clockSeq, hardwareAddr := getStorage() + + switch domain { + case DomainPerson: + binary.BigEndian.PutUint32(u[0:], posixUID) + case DomainGroup: + binary.BigEndian.PutUint32(u[0:], posixGID) + } + + binary.BigEndian.PutUint16(u[4:], uint16(timeNow>>32)) + binary.BigEndian.PutUint16(u[6:], uint16(timeNow>>48)) + binary.BigEndian.PutUint16(u[8:], clockSeq) + u[9] = domain + + copy(u[10:], hardwareAddr) + + u.SetVersion(2) + u.SetVariant() + + return u +} + +// NewV3 returns UUID based on MD5 hash of namespace UUID and name. +func NewV3(ns UUID, name string) UUID { + u := newFromHash(md5.New(), ns, name) + u.SetVersion(3) + u.SetVariant() + + return u +} + +// NewV4 returns random generated UUID. +func NewV4() UUID { + u := UUID{} + safeRandom(u[:]) + u.SetVersion(4) + u.SetVariant() + + return u +} + +// NewV5 returns UUID based on SHA-1 hash of namespace UUID and name. +func NewV5(ns UUID, name string) UUID { + u := newFromHash(sha1.New(), ns, name) + u.SetVersion(5) + u.SetVariant() + + return u +} + +// Returns UUID based on hashing of namespace UUID and name. +func newFromHash(h hash.Hash, ns UUID, name string) UUID { + u := UUID{} + h.Write(ns[:]) + h.Write([]byte(name)) + copy(u[:], h.Sum(nil)) + + return u +} diff --git a/cmd/ponzu/vendor/github.com/satori/go.uuid/uuid_test.go b/cmd/ponzu/vendor/github.com/satori/go.uuid/uuid_test.go new file mode 100644 index 0000000..5650480 --- /dev/null +++ b/cmd/ponzu/vendor/github.com/satori/go.uuid/uuid_test.go @@ -0,0 +1,633 @@ +// Copyright (C) 2013, 2015 by Maxim Bublis <b@codemonkey.ru> +// +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +package uuid + +import ( + "bytes" + "testing" +) + +func TestBytes(t *testing.T) { + u := UUID{0x6b, 0xa7, 0xb8, 0x10, 0x9d, 0xad, 0x11, 0xd1, 0x80, 0xb4, 0x00, 0xc0, 0x4f, 0xd4, 0x30, 0xc8} + + bytes1 := []byte{0x6b, 0xa7, 0xb8, 0x10, 0x9d, 0xad, 0x11, 0xd1, 0x80, 0xb4, 0x00, 0xc0, 0x4f, 0xd4, 0x30, 0xc8} + + if !bytes.Equal(u.Bytes(), bytes1) { + t.Errorf("Incorrect bytes representation for UUID: %s", u) + } +} + +func TestString(t *testing.T) { + if NamespaceDNS.String() != "6ba7b810-9dad-11d1-80b4-00c04fd430c8" { + t.Errorf("Incorrect string representation for UUID: %s", NamespaceDNS.String()) + } +} + +func TestEqual(t *testing.T) { + if !Equal(NamespaceDNS, NamespaceDNS) { + t.Errorf("Incorrect comparison of %s and %s", NamespaceDNS, NamespaceDNS) + } + + if Equal(NamespaceDNS, NamespaceURL) { + t.Errorf("Incorrect comparison of %s and %s", NamespaceDNS, NamespaceURL) + } +} + +func TestOr(t *testing.T) { + u1 := UUID{0x00, 0xff, 0x00, 0xff, 0x00, 0xff, 0x00, 0xff, 0x00, 0xff, 0x00, 0xff, 0x00, 0xff, 0x00, 0xff} + u2 := UUID{0xff, 0x00, 0xff, 0x00, 0xff, 0x00, 0xff, 0x00, 0xff, 0x00, 0xff, 0x00, 0xff, 0x00, 0xff, 0x00} + + u := UUID{0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff} + + if !Equal(u, Or(u1, u2)) { + t.Errorf("Incorrect bitwise OR result %s", Or(u1, u2)) + } +} + +func TestAnd(t *testing.T) { + u1 := UUID{0x00, 0xff, 0x00, 0xff, 0x00, 0xff, 0x00, 0xff, 0x00, 0xff, 0x00, 0xff, 0x00, 0xff, 0x00, 0xff} + u2 := UUID{0xff, 0x00, 0xff, 0x00, 0xff, 0x00, 0xff, 0x00, 0xff, 0x00, 0xff, 0x00, 0xff, 0x00, 0xff, 0x00} + + u := UUID{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00} + + if !Equal(u, And(u1, u2)) { + t.Errorf("Incorrect bitwise AND result %s", And(u1, u2)) + } +} + +func TestVersion(t *testing.T) { + u := UUID{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00} + + if u.Version() != 1 { + t.Errorf("Incorrect version for UUID: %d", u.Version()) + } +} + +func TestSetVersion(t *testing.T) { + u := UUID{} + u.SetVersion(4) + + if u.Version() != 4 { + t.Errorf("Incorrect version for UUID after u.setVersion(4): %d", u.Version()) + } +} + +func TestVariant(t *testing.T) { + u1 := UUID{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00} + + if u1.Variant() != VariantNCS { + t.Errorf("Incorrect variant for UUID variant %d: %d", VariantNCS, u1.Variant()) + } + + u2 := UUID{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00} + + if u2.Variant() != VariantRFC4122 { + t.Errorf("Incorrect variant for UUID variant %d: %d", VariantRFC4122, u2.Variant()) + } + + u3 := UUID{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xc0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00} + + if u3.Variant() != VariantMicrosoft { + t.Errorf("Incorrect variant for UUID variant %d: %d", VariantMicrosoft, u3.Variant()) + } + + u4 := UUID{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xe0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00} + + if u4.Variant() != VariantFuture { + t.Errorf("Incorrect variant for UUID variant %d: %d", VariantFuture, u4.Variant()) + } +} + +func TestSetVariant(t *testing.T) { + u := new(UUID) + u.SetVariant() + + if u.Variant() != VariantRFC4122 { + t.Errorf("Incorrect variant for UUID after u.setVariant(): %d", u.Variant()) + } +} + +func TestFromBytes(t *testing.T) { + u := UUID{0x6b, 0xa7, 0xb8, 0x10, 0x9d, 0xad, 0x11, 0xd1, 0x80, 0xb4, 0x00, 0xc0, 0x4f, 0xd4, 0x30, 0xc8} + b1 := []byte{0x6b, 0xa7, 0xb8, 0x10, 0x9d, 0xad, 0x11, 0xd1, 0x80, 0xb4, 0x00, 0xc0, 0x4f, 0xd4, 0x30, 0xc8} + + u1, err := FromBytes(b1) + if err != nil { + t.Errorf("Error parsing UUID from bytes: %s", err) + } + + if !Equal(u, u1) { + t.Errorf("UUIDs should be equal: %s and %s", u, u1) + } + + b2 := []byte{} + + _, err = FromBytes(b2) + if err == nil { + t.Errorf("Should return error parsing from empty byte slice, got %s", err) + } +} + +func TestMarshalBinary(t *testing.T) { + u := UUID{0x6b, 0xa7, 0xb8, 0x10, 0x9d, 0xad, 0x11, 0xd1, 0x80, 0xb4, 0x00, 0xc0, 0x4f, 0xd4, 0x30, 0xc8} + b1 := []byte{0x6b, 0xa7, 0xb8, 0x10, 0x9d, 0xad, 0x11, 0xd1, 0x80, 0xb4, 0x00, 0xc0, 0x4f, 0xd4, 0x30, 0xc8} + + b2, err := u.MarshalBinary() + if err != nil { + t.Errorf("Error marshaling UUID: %s", err) + } + + if !bytes.Equal(b1, b2) { + t.Errorf("Marshaled UUID should be %s, got %s", b1, b2) + } +} + +func TestUnmarshalBinary(t *testing.T) { + u := UUID{0x6b, 0xa7, 0xb8, 0x10, 0x9d, 0xad, 0x11, 0xd1, 0x80, 0xb4, 0x00, 0xc0, 0x4f, 0xd4, 0x30, 0xc8} + b1 := []byte{0x6b, 0xa7, 0xb8, 0x10, 0x9d, 0xad, 0x11, 0xd1, 0x80, 0xb4, 0x00, 0xc0, 0x4f, 0xd4, 0x30, 0xc8} + + u1 := UUID{} + err := u1.UnmarshalBinary(b1) + if err != nil { + t.Errorf("Error unmarshaling UUID: %s", err) + } + + if !Equal(u, u1) { + t.Errorf("UUIDs should be equal: %s and %s", u, u1) + } + + b2 := []byte{} + u2 := UUID{} + + err = u2.UnmarshalBinary(b2) + if err == nil { + t.Errorf("Should return error unmarshalling from empty byte slice, got %s", err) + } +} + +func TestFromString(t *testing.T) { + u := UUID{0x6b, 0xa7, 0xb8, 0x10, 0x9d, 0xad, 0x11, 0xd1, 0x80, 0xb4, 0x00, 0xc0, 0x4f, 0xd4, 0x30, 0xc8} + + s1 := "6ba7b810-9dad-11d1-80b4-00c04fd430c8" + s2 := "{6ba7b810-9dad-11d1-80b4-00c04fd430c8}" + s3 := "urn:uuid:6ba7b810-9dad-11d1-80b4-00c04fd430c8" + + _, err := FromString("") + if err == nil { + t.Errorf("Should return error trying to parse empty string, got %s", err) + } + + u1, err := FromString(s1) + if err != nil { + t.Errorf("Error parsing UUID from string: %s", err) + } + + if !Equal(u, u1) { + t.Errorf("UUIDs should be equal: %s and %s", u, u1) + } + + u2, err := FromString(s2) + if err != nil { + t.Errorf("Error parsing UUID from string: %s", err) + } + + if !Equal(u, u2) { + t.Errorf("UUIDs should be equal: %s and %s", u, u2) + } + + u3, err := FromString(s3) + if err != nil { + t.Errorf("Error parsing UUID from string: %s", err) + } + + if !Equal(u, u3) { + t.Errorf("UUIDs should be equal: %s and %s", u, u3) + } +} + +func TestFromStringShort(t *testing.T) { + // Invalid 35-character UUID string + s1 := "6ba7b810-9dad-11d1-80b4-00c04fd430c" + + for i := len(s1); i >= 0; i-- { + _, err := FromString(s1[:i]) + if err == nil { + t.Errorf("Should return error trying to parse too short string, got %s", err) + } + } +} + +func TestFromStringLong(t *testing.T) { + // Invalid 37+ character UUID string + s := []string{ + "6ba7b810-9dad-11d1-80b4-00c04fd430c8=", + "6ba7b810-9dad-11d1-80b4-00c04fd430c8}", + "{6ba7b810-9dad-11d1-80b4-00c04fd430c8}f", + "6ba7b810-9dad-11d1-80b4-00c04fd430c800c04fd430c8", + } + + for _, str := range s { + _, err := FromString(str) + if err == nil { + t.Errorf("Should return error trying to parse too long string, passed %s", str) + } + } +} + +func TestFromStringInvalid(t *testing.T) { + // Invalid UUID string formats + s := []string{ + "6ba7b8109dad11d180b400c04fd430c8", + "6ba7b8109dad11d180b400c04fd430c86ba7b8109dad11d180b400c04fd430c8", + "urn:uuid:{6ba7b810-9dad-11d1-80b4-00c04fd430c8}", + "6ba7b8109-dad-11d1-80b4-00c04fd430c8", + "6ba7b810-9dad1-1d1-80b4-00c04fd430c8", + "6ba7b810-9dad-11d18-0b4-00c04fd430c8", + "6ba7b810-9dad-11d1-80b40-0c04fd430c8", + "6ba7b810+9dad+11d1+80b4+00c04fd430c8", + "6ba7b810-9dad11d180b400c04fd430c8", + "6ba7b8109dad-11d180b400c04fd430c8", + "6ba7b8109dad11d1-80b400c04fd430c8", + "6ba7b8109dad11d180b4-00c04fd430c8", + } + + for _, str := range s { + _, err := FromString(str) + if err == nil { + t.Errorf("Should return error trying to parse invalid string, passed %s", str) + } + } +} + +func TestFromStringOrNil(t *testing.T) { + u := FromStringOrNil("") + if u != Nil { + t.Errorf("Should return Nil UUID on parse failure, got %s", u) + } +} + +func TestFromBytesOrNil(t *testing.T) { + b := []byte{} + u := FromBytesOrNil(b) + if u != Nil { + t.Errorf("Should return Nil UUID on parse failure, got %s", u) + } +} + +func TestMarshalText(t *testing.T) { + u := UUID{0x6b, 0xa7, 0xb8, 0x10, 0x9d, 0xad, 0x11, 0xd1, 0x80, 0xb4, 0x00, 0xc0, 0x4f, 0xd4, 0x30, 0xc8} + b1 := []byte("6ba7b810-9dad-11d1-80b4-00c04fd430c8") + + b2, err := u.MarshalText() + if err != nil { + t.Errorf("Error marshaling UUID: %s", err) + } + + if !bytes.Equal(b1, b2) { + t.Errorf("Marshaled UUID should be %s, got %s", b1, b2) + } +} + +func TestUnmarshalText(t *testing.T) { + u := UUID{0x6b, 0xa7, 0xb8, 0x10, 0x9d, 0xad, 0x11, 0xd1, 0x80, 0xb4, 0x00, 0xc0, 0x4f, 0xd4, 0x30, 0xc8} + b1 := []byte("6ba7b810-9dad-11d1-80b4-00c04fd430c8") + + u1 := UUID{} + err := u1.UnmarshalText(b1) + if err != nil { + t.Errorf("Error unmarshaling UUID: %s", err) + } + + if !Equal(u, u1) { + t.Errorf("UUIDs should be equal: %s and %s", u, u1) + } + + b2 := []byte("") + u2 := UUID{} + + err = u2.UnmarshalText(b2) + if err == nil { + t.Errorf("Should return error trying to unmarshal from empty string") + } +} + +func TestValue(t *testing.T) { + u, err := FromString("6ba7b810-9dad-11d1-80b4-00c04fd430c8") + if err != nil { + t.Errorf("Error parsing UUID from string: %s", err) + } + + val, err := u.Value() + if err != nil { + t.Errorf("Error getting UUID value: %s", err) + } + + if val != u.String() { + t.Errorf("Wrong value returned, should be equal: %s and %s", val, u) + } +} + +func TestValueNil(t *testing.T) { + u := UUID{} + + val, err := u.Value() + if err != nil { + t.Errorf("Error getting UUID value: %s", err) + } + + if val != Nil.String() { + t.Errorf("Wrong value returned, should be equal to UUID.Nil: %s", val) + } +} + +func TestNullUUIDValueNil(t *testing.T) { + u := NullUUID{} + + val, err := u.Value() + if err != nil { + t.Errorf("Error getting UUID value: %s", err) + } + + if val != nil { + t.Errorf("Wrong value returned, should be nil: %s", val) + } +} + +func TestScanBinary(t *testing.T) { + u := UUID{0x6b, 0xa7, 0xb8, 0x10, 0x9d, 0xad, 0x11, 0xd1, 0x80, 0xb4, 0x00, 0xc0, 0x4f, 0xd4, 0x30, 0xc8} + b1 := []byte{0x6b, 0xa7, 0xb8, 0x10, 0x9d, 0xad, 0x11, 0xd1, 0x80, 0xb4, 0x00, 0xc0, 0x4f, 0xd4, 0x30, 0xc8} + + u1 := UUID{} + err := u1.Scan(b1) + if err != nil { + t.Errorf("Error unmarshaling UUID: %s", err) + } + + if !Equal(u, u1) { + t.Errorf("UUIDs should be equal: %s and %s", u, u1) + } + + b2 := []byte{} + u2 := UUID{} + + err = u2.Scan(b2) + if err == nil { + t.Errorf("Should return error unmarshalling from empty byte slice, got %s", err) + } +} + +func TestScanString(t *testing.T) { + u := UUID{0x6b, 0xa7, 0xb8, 0x10, 0x9d, 0xad, 0x11, 0xd1, 0x80, 0xb4, 0x00, 0xc0, 0x4f, 0xd4, 0x30, 0xc8} + s1 := "6ba7b810-9dad-11d1-80b4-00c04fd430c8" + + u1 := UUID{} + err := u1.Scan(s1) + if err != nil { + t.Errorf("Error unmarshaling UUID: %s", err) + } + + if !Equal(u, u1) { + t.Errorf("UUIDs should be equal: %s and %s", u, u1) + } + + s2 := "" + u2 := UUID{} + + err = u2.Scan(s2) + if err == nil { + t.Errorf("Should return error trying to unmarshal from empty string") + } +} + +func TestScanText(t *testing.T) { + u := UUID{0x6b, 0xa7, 0xb8, 0x10, 0x9d, 0xad, 0x11, 0xd1, 0x80, 0xb4, 0x00, 0xc0, 0x4f, 0xd4, 0x30, 0xc8} + b1 := []byte("6ba7b810-9dad-11d1-80b4-00c04fd430c8") + + u1 := UUID{} + err := u1.Scan(b1) + if err != nil { + t.Errorf("Error unmarshaling UUID: %s", err) + } + + if !Equal(u, u1) { + t.Errorf("UUIDs should be equal: %s and %s", u, u1) + } + + b2 := []byte("") + u2 := UUID{} + + err = u2.Scan(b2) + if err == nil { + t.Errorf("Should return error trying to unmarshal from empty string") + } +} + +func TestScanUnsupported(t *testing.T) { + u := UUID{} + + err := u.Scan(true) + if err == nil { + t.Errorf("Should return error trying to unmarshal from bool") + } +} + +func TestScanNil(t *testing.T) { + u := UUID{0x6b, 0xa7, 0xb8, 0x10, 0x9d, 0xad, 0x11, 0xd1, 0x80, 0xb4, 0x00, 0xc0, 0x4f, 0xd4, 0x30, 0xc8} + + err := u.Scan(nil) + if err == nil { + t.Errorf("Error UUID shouldn't allow unmarshalling from nil") + } +} + +func TestNullUUIDScanValid(t *testing.T) { + u := UUID{0x6b, 0xa7, 0xb8, 0x10, 0x9d, 0xad, 0x11, 0xd1, 0x80, 0xb4, 0x00, 0xc0, 0x4f, 0xd4, 0x30, 0xc8} + s1 := "6ba7b810-9dad-11d1-80b4-00c04fd430c8" + + u1 := NullUUID{} + err := u1.Scan(s1) + if err != nil { + t.Errorf("Error unmarshaling NullUUID: %s", err) + } + + if !u1.Valid { + t.Errorf("NullUUID should be valid") + } + + if !Equal(u, u1.UUID) { + t.Errorf("UUIDs should be equal: %s and %s", u, u1.UUID) + } +} + +func TestNullUUIDScanNil(t *testing.T) { + u := NullUUID{UUID{0x6b, 0xa7, 0xb8, 0x10, 0x9d, 0xad, 0x11, 0xd1, 0x80, 0xb4, 0x00, 0xc0, 0x4f, 0xd4, 0x30, 0xc8}, true} + + err := u.Scan(nil) + if err != nil { + t.Errorf("Error unmarshaling NullUUID: %s", err) + } + + if u.Valid { + t.Errorf("NullUUID should not be valid") + } + + if !Equal(u.UUID, Nil) { + t.Errorf("NullUUID value should be equal to Nil: %v", u) + } +} + +func TestNewV1(t *testing.T) { + u := NewV1() + + if u.Version() != 1 { + t.Errorf("UUIDv1 generated with incorrect version: %d", u.Version()) + } + + if u.Variant() != VariantRFC4122 { + t.Errorf("UUIDv1 generated with incorrect variant: %d", u.Variant()) + } + + u1 := NewV1() + u2 := NewV1() + + if Equal(u1, u2) { + t.Errorf("UUIDv1 generated two equal UUIDs: %s and %s", u1, u2) + } + + oldFunc := epochFunc + epochFunc = func() uint64 { return 0 } + + u3 := NewV1() + u4 := NewV1() + + if Equal(u3, u4) { + t.Errorf("UUIDv1 generated two equal UUIDs: %s and %s", u3, u4) + } + + epochFunc = oldFunc +} + +func TestNewV2(t *testing.T) { + u1 := NewV2(DomainPerson) + + if u1.Version() != 2 { + t.Errorf("UUIDv2 generated with incorrect version: %d", u1.Version()) + } + + if u1.Variant() != VariantRFC4122 { + t.Errorf("UUIDv2 generated with incorrect variant: %d", u1.Variant()) + } + + u2 := NewV2(DomainGroup) + + if u2.Version() != 2 { + t.Errorf("UUIDv2 generated with incorrect version: %d", u2.Version()) + } + + if u2.Variant() != VariantRFC4122 { + t.Errorf("UUIDv2 generated with incorrect variant: %d", u2.Variant()) + } +} + +func TestNewV3(t *testing.T) { + u := NewV3(NamespaceDNS, "www.example.com") + + if u.Version() != 3 { + t.Errorf("UUIDv3 generated with incorrect version: %d", u.Version()) + } + + if u.Variant() != VariantRFC4122 { + t.Errorf("UUIDv3 generated with incorrect variant: %d", u.Variant()) + } + + if u.String() != "5df41881-3aed-3515-88a7-2f4a814cf09e" { + t.Errorf("UUIDv3 generated incorrectly: %s", u.String()) + } + + u = NewV3(NamespaceDNS, "python.org") + + if u.String() != "6fa459ea-ee8a-3ca4-894e-db77e160355e" { + t.Errorf("UUIDv3 generated incorrectly: %s", u.String()) + } + + u1 := NewV3(NamespaceDNS, "golang.org") + u2 := NewV3(NamespaceDNS, "golang.org") + if !Equal(u1, u2) { + t.Errorf("UUIDv3 generated different UUIDs for same namespace and name: %s and %s", u1, u2) + } + + u3 := NewV3(NamespaceDNS, "example.com") + if Equal(u1, u3) { + t.Errorf("UUIDv3 generated same UUIDs for different names in same namespace: %s and %s", u1, u2) + } + + u4 := NewV3(NamespaceURL, "golang.org") + if Equal(u1, u4) { + t.Errorf("UUIDv3 generated same UUIDs for sane names in different namespaces: %s and %s", u1, u4) + } +} + +func TestNewV4(t *testing.T) { + u := NewV4() + + if u.Version() != 4 { + t.Errorf("UUIDv4 generated with incorrect version: %d", u.Version()) + } + + if u.Variant() != VariantRFC4122 { + t.Errorf("UUIDv4 generated with incorrect variant: %d", u.Variant()) + } +} + +func TestNewV5(t *testing.T) { + u := NewV5(NamespaceDNS, "www.example.com") + + if u.Version() != 5 { + t.Errorf("UUIDv5 generated with incorrect version: %d", u.Version()) + } + + if u.Variant() != VariantRFC4122 { + t.Errorf("UUIDv5 generated with incorrect variant: %d", u.Variant()) + } + + u = NewV5(NamespaceDNS, "python.org") + + if u.String() != "886313e1-3b8a-5372-9b90-0c9aee199e5d" { + t.Errorf("UUIDv5 generated incorrectly: %s", u.String()) + } + + u1 := NewV5(NamespaceDNS, "golang.org") + u2 := NewV5(NamespaceDNS, "golang.org") + if !Equal(u1, u2) { + t.Errorf("UUIDv5 generated different UUIDs for same namespace and name: %s and %s", u1, u2) + } + + u3 := NewV5(NamespaceDNS, "example.com") + if Equal(u1, u3) { + t.Errorf("UUIDv5 generated same UUIDs for different names in same namespace: %s and %s", u1, u2) + } + + u4 := NewV5(NamespaceURL, "golang.org") + if Equal(u1, u4) { + t.Errorf("UUIDv3 generated same UUIDs for sane names in different namespaces: %s and %s", u1, u4) + } +} diff --git a/content/item.go b/content/item.go index c847ed7..9eb3c16 100644 --- a/content/item.go +++ b/content/item.go @@ -1,6 +1,10 @@ package content -import "net/http" +import ( + "net/http" + + uuid "github.com/satori/go.uuid" +) // Sluggable makes a struct locatable by URL with it's own path // As an Item implementing Sluggable, slugs may overlap. If this is an issue, @@ -10,12 +14,14 @@ type Sluggable interface { SetSlug(string) } -// Identifiable enables a struct to have its ID set. Typically this is done +// Identifiable enables a struct to have its ID set/get. Typically this is done // to set an ID to -1 indicating it is new for DB inserts, since by default // a newly initialized struct would have an ID of 0, the int zero-value, and // BoltDB's starting key per bucket is 0, thus overwriting the first record. type Identifiable interface { + ItemID() int SetItemID(int) + UniqueID() uuid.UUID } // Hookable provides our user with an easy way to intercept or add functionality @@ -35,13 +41,13 @@ type Hookable interface { AfterReject(req *http.Request) error } - // Item should only be embedded into content type structs. type Item struct { - ID int `json:"id"` - Slug string `json:"slug"` - Timestamp int64 `json:"timestamp"` - Updated int64 `json:"updated"` + UUID uuid.UUID `json:"uuid"` + ID int `json:"id"` + Slug string `json:"slug"` + Timestamp int64 `json:"timestamp"` + Updated int64 `json:"updated"` } // Time partially implements the Sortable interface @@ -54,21 +60,29 @@ func (i Item) Touch() int64 { return i.Updated } -// ItemID partially implements the Sortable interface -func (i Item) ItemID() int { - return i.ID -} - // SetSlug sets the item's slug for its URL func (i *Item) SetSlug(slug string) { i.Slug = slug } +// ItemID gets the Item's ID field +// partially implements the Identifiable interface +func (i Item) ItemID() int { + return i.ID +} + // SetItemID sets the Item's ID field +// partially implements the Identifiable interface func (i *Item) SetItemID(id int) { i.ID = id } +// UniqueID gets the Item's UUID field +// partially implements the Identifiable interface +func (i Item) UniqueID() uuid.UUID { + return i.UUID +} + // BeforeSave is a no-op to ensure structs which embed Item implement Hookable func (i Item) BeforeSave(req *http.Request) error { return nil @@ -107,4 +121,4 @@ func (i Item) BeforeReject(req *http.Request) error { // AfterReject is a no-op to ensure structs which embed Item implement Hookable func (i Item) AfterReject(req *http.Request) error { return nil -}
\ No newline at end of file +} diff --git a/content/post.go b/content/post.go deleted file mode 100644 index b81bb5a..0000000 --- a/content/post.go +++ /dev/null @@ -1,83 +0,0 @@ -package content - -import ( - "fmt" - - "github.com/bosssauce/ponzu/management/editor" -) - -// Post is the generic content struct -type Post struct { - Item - editor editor.Editor - - Title string `json:"title"` - Content string `json:"content"` - Photo string `json:"photo"` - Author string `json:"author"` - Category []string `json:"category"` - Theme string `json:"theme"` -} - -// MarshalEditor writes a buffer of html to edit a Post and partially implements editor.Editable -func (p *Post) MarshalEditor() ([]byte, error) { - view, err := editor.Form(p, - editor.Field{ - View: editor.Input("Title", p, map[string]string{ - "label": "Post Title", - "type": "text", - "placeholder": "Enter your Post Title here", - }), - }, - editor.Field{ - View: editor.Richtext("Content", p, map[string]string{ - "label": "Content", - "placeholder": "Add the content of your post here", - }), - }, - editor.Field{ - View: editor.File("Photo", p, map[string]string{ - "label": "Author Photo", - "placeholder": "Upload a profile picture for the author", - }), - }, - editor.Field{ - View: editor.Input("Author", p, map[string]string{ - "label": "Author", - "type": "text", - "placeholder": "Enter the author name here", - }), - }, - editor.Field{ - View: editor.Tags("Category", p, map[string]string{ - "label": "Post Categories", - }), - }, - editor.Field{ - View: editor.Select("Theme", p, map[string]string{ - "label": "Theme Style", - }, map[string]string{ - "dark": "Dark", - "light": "Light", - }), - }, - ) - - if err != nil { - return nil, fmt.Errorf("Failed to render Post editor view: %s", err.Error()) - } - - return view, nil -} - -func init() { - Types["Post"] = func() interface{} { return new(Post) } -} - -// ContentName is required to set the display name for a piece of content in the editor -// Partially implements editor.Editable -func (p *Post) ContentName() string { return p.Title } - -// Editor is a buffer of bytes for the Form function to write input views -// partially implements editor.Editable -func (p *Post) Editor() *editor.Editor { return &p.editor } diff --git a/management/editor/editor.go b/management/editor/editor.go index 3843592..2a9183b 100644 --- a/management/editor/editor.go +++ b/management/editor/editor.go @@ -18,7 +18,6 @@ type Editable interface { type Sortable interface { Time() int64 Touch() int64 - ItemID() int } // Mergeable allows external post content to be approved and published through diff --git a/management/manager/manager.go b/management/manager/manager.go index 2830ba4..6083f73 100644 --- a/management/manager/manager.go +++ b/management/manager/manager.go @@ -5,12 +5,15 @@ import ( "fmt" "html/template" + "github.com/bosssauce/ponzu/content" "github.com/bosssauce/ponzu/management/editor" + uuid "github.com/satori/go.uuid" ) const managerHTML = ` <div class="card editor"> <form method="post" action="/admin/edit" enctype="multipart/form-data"> + <input type="hidden" name="uuid" value="{{.UUID}}"/> <input type="hidden" name="id" value="{{.ID}}"/> <input type="hidden" name="type" value="{{.Kind}}"/> {{ .Editor }} @@ -104,6 +107,7 @@ const managerHTML = ` type manager struct { ID int + UUID uuid.UUID Kind string Editor template.HTML } @@ -115,13 +119,14 @@ func Manage(e editor.Editable, typeName string) ([]byte, error) { return nil, fmt.Errorf("Couldn't marshal editor for content %s. %s", typeName, err.Error()) } - s, ok := e.(editor.Sortable) + i, ok := e.(content.Identifiable) if !ok { - return nil, fmt.Errorf("Content type %s does not implement content.Identifiable.", typeName) + return nil, fmt.Errorf("Content type %s does not implement content.Sortable.", typeName) } m := manager{ - ID: s.ItemID(), + ID: i.ItemID(), + UUID: i.UniqueID(), Kind: typeName, Editor: template.HTML(v), } diff --git a/system/admin/filesystem.go b/system/admin/filesystem.go index 4e64a26..77c721e 100644 --- a/system/admin/filesystem.go +++ b/system/admin/filesystem.go @@ -1,13 +1,12 @@ package admin import ( - "net/http" + "net/http" "os" ) - func restrict(dir http.Dir) justFilesFilesystem { - return justFilesFilesystem{dir} + return justFilesFilesystem{dir} } // the code below removes the open directory listing when accessing a URL which @@ -16,21 +15,21 @@ func restrict(dir http.Dir) justFilesFilesystem { // credit: Brad Fitzpatrick (c) 2012 type justFilesFilesystem struct { - fs http.FileSystem + fs http.FileSystem } func (fs justFilesFilesystem) Open(name string) (http.File, error) { - f, err := fs.fs.Open(name) - if err != nil { - return nil, err - } - return neuteredReaddirFile{f}, nil + f, err := fs.fs.Open(name) + if err != nil { + return nil, err + } + return neuteredReaddirFile{f}, nil } type neuteredReaddirFile struct { - http.File + http.File } func (f neuteredReaddirFile) Readdir(count int) ([]os.FileInfo, error) { - return nil, nil + return nil, nil } diff --git a/system/admin/handlers.go b/system/admin/handlers.go index 38ce41d..7b8bfae 100644 --- a/system/admin/handlers.go +++ b/system/admin/handlers.go @@ -908,7 +908,7 @@ func postsHandler(res http.ResponseWriter, req *http.Request) { b.Write(post) } } - + html += `<ul class="posts row">` b.Write([]byte(`</ul></div></div>`)) @@ -947,7 +947,14 @@ func adminPostListItem(e editor.Editable, typeName, status string) []byte { s, ok := e.(editor.Sortable) if !ok { log.Println("Content type", typeName, "doesn't implement editor.Sortable") - post := `<li class="col s12">Error retreiving data. Your data type doesn't implement necessary interfaces.</li>` + post := `<li class="col s12">Error retreiving data. Your data type doesn't implement necessary interfaces. (editor.Sortable)</li>` + return []byte(post) + } + + i, ok := e.(content.Identifiable) + if !ok { + log.Println("Content type", typeName, "doesn't implement content.Identifiable") + post := `<li class="col s12">Error retreiving data. Your data type doesn't implement necessary interfaces. (content.Identifiable)</li>` return []byte(post) } @@ -957,7 +964,7 @@ func adminPostListItem(e editor.Editable, typeName, status string) []byte { updatedTime := upTime.Format("01/02/06 03:04 PM") publishTime := tsTime.Format("01/02/06") - cid := fmt.Sprintf("%d", s.ItemID()) + cid := fmt.Sprintf("%d", i.ItemID()) switch status { case "public", "": @@ -1200,7 +1207,6 @@ func editHandler(res http.ResponseWriter, req *http.Request) { return } s.SetItemID(-1) - } m, err := manager.Manage(post.(editor.Editable), t) diff --git a/system/api/external.go b/system/api/external.go index 5c50172..4e008af 100644 --- a/system/api/external.go +++ b/system/api/external.go @@ -13,7 +13,7 @@ import ( ) // Externalable accepts or rejects external POST requests to endpoints such as: -// /external/posts?type=Review +// /external/content?type=Review type Externalable interface { // Accepts determines whether a type will allow external submissions Accepts() bool diff --git a/system/db/content.go b/system/db/content.go index b43d611..0cfadf4 100644 --- a/system/db/content.go +++ b/system/db/content.go @@ -16,6 +16,7 @@ import ( "github.com/boltdb/bolt" "github.com/gorilla/schema" + uuid "github.com/satori/go.uuid" ) // SetContent inserts or updates values in the database. @@ -107,6 +108,10 @@ func insert(ns string, data url.Values) (int, error) { } data.Set("id", cid) + // add UUID to data for use in embedded Item + uid := uuid.NewV4() + data.Set("uuid", uid.String()) + j, err := postToJSON(ns, data) if err != nil { return err |