From 40e4766bff16494db3f126b2340dc4a0c9ec2508 Mon Sep 17 00:00:00 2001 From: Henrique Dias Date: Sat, 26 Sep 2015 22:02:49 +0100 Subject: [PATCH] split functions --- browse/browse.go | 201 +---------------------------- browse/delete.go | 37 ++++++ browse/get.go | 43 +++++++ browse/post.go | 140 ++++++++++++++++++++ editor/editor.go | 289 +----------------------------------------- editor/get.go | 147 +++++++++++++++++++++ editor/post.go | 175 +++++++++++++++++++++++++ templates/editor.tmpl | 2 +- 8 files changed, 551 insertions(+), 483 deletions(-) create mode 100644 browse/delete.go create mode 100644 browse/get.go create mode 100644 browse/post.go create mode 100644 editor/get.go create mode 100644 editor/post.go diff --git a/browse/browse.go b/browse/browse.go index 4a21c210..e7517ebb 100644 --- a/browse/browse.go +++ b/browse/browse.go @@ -1,20 +1,10 @@ package browse import ( - "bytes" - "encoding/json" - "errors" - "io" - "mime/multipart" "net/http" - "os" "strings" - "text/template" "github.com/hacdias/caddy-hugo/config" - "github.com/hacdias/caddy-hugo/utils" - "github.com/mholt/caddy/middleware" - "github.com/mholt/caddy/middleware/browse" ) // ServeHTTP is used to serve the content of Browse page @@ -25,197 +15,12 @@ func ServeHTTP(w http.ResponseWriter, r *http.Request, c *config.Config) (int, e switch r.Method { case "DELETE": - return delete(w, r) + return DELETE(w, r) case "POST": - return post(w, r) + return POST(w, r) case "GET": - return get(w, r, c) + return GET(w, r, c) default: return 400, nil } } - -func delete(w http.ResponseWriter, r *http.Request) (int, error) { - // Remove both beginning and trailing slashes - r.URL.Path = strings.TrimPrefix(r.URL.Path, "/") - r.URL.Path = strings.TrimSuffix(r.URL.Path, "/") - - // Check if the file or directory exists - if stat, err := os.Stat(r.URL.Path); err == nil { - var err error - // If it's dir, remove all of the content inside - if stat.IsDir() { - err = os.RemoveAll(r.URL.Path) - } else { - err = os.Remove(r.URL.Path) - } - - // Check for errors - if err != nil { - w.Write([]byte(err.Error())) - return 500, err - } - } else { - return 404, nil - } - - w.Header().Set("Content-Type", "application/json") - w.Write([]byte("{}")) - return 200, nil -} - -func post(w http.ResponseWriter, r *http.Request) (int, error) { - // Remove both beginning slashes - r.URL.Path = strings.TrimPrefix(r.URL.Path, "/") - - // If it's the upload of a file - if r.Header.Get("X-Upload") == "true" { - return upload(w, r) - } - - // Get the JSON information sent using a buffer - buffer := new(bytes.Buffer) - buffer.ReadFrom(r.Body) - - // Creates the raw file "map" using the JSON - var info map[string]interface{} - json.Unmarshal(buffer.Bytes(), &info) - - // Check if filename and archetype are specified in - // the request - if _, ok := info["filename"]; !ok { - return 400, errors.New("Filename not specified.") - } - - if _, ok := info["archetype"]; !ok { - return 400, errors.New("Archtype not specified.") - } - - // Sanitize the file name path - filename := info["filename"].(string) - filename = strings.TrimPrefix(filename, "/") - filename = strings.TrimSuffix(filename, "/") - filename = r.URL.Path + filename - - // Check if the archetype is defined - if info["archetype"] != "" { - // Sanitize the archetype path - archetype := info["archetype"].(string) - archetype = strings.Replace(archetype, "/archetypes", "", 1) - archetype = strings.Replace(archetype, "archetypes", "", 1) - archetype = strings.TrimPrefix(archetype, "/") - archetype = strings.TrimSuffix(archetype, "/") - archetype = "archetypes/" + archetype - - // Check if the archetype ending with .markdown exists - if _, err := os.Stat(archetype + ".markdown"); err == nil { - err = utils.CopyFile(archetype+".markdown", filename) - if err != nil { - w.Write([]byte(err.Error())) - return 500, err - } - - w.Header().Set("Location", "/admin/edit/"+filename) - w.Header().Set("Content-Type", "application/json") - w.Write([]byte("{}")) - return 201, nil - } - - // Check if the archetype ending with .md exists - if _, err := os.Stat(archetype + ".md"); err == nil { - err = utils.CopyFile(archetype+".md", filename) - if err != nil { - w.Write([]byte(err.Error())) - return 500, err - } - - w.Header().Set("Location", "/admin/edit/"+filename) - w.Header().Set("Content-Type", "application/json") - w.Write([]byte("{}")) - return 201, nil - } - } - - wf, err := os.Create(filename) - if err != nil { - w.Write([]byte(err.Error())) - return 500, err - } - - defer wf.Close() - - w.Header().Set("Location", "/admin/edit/"+filename) - w.Header().Set("Content-Type", "application/json") - w.Write([]byte("{}")) - return 200, nil -} - -func upload(w http.ResponseWriter, r *http.Request) (int, error) { - // Parse the multipart form in the request - err := r.ParseMultipartForm(100000) - if err != nil { - w.Write([]byte(err.Error())) - return 500, err - } - - // For each file header in the multipart form - for _, fheaders := range r.MultipartForm.File { - // Handle each file - for _, hdr := range fheaders { - // Open the first file - var infile multipart.File - if infile, err = hdr.Open(); nil != err { - w.Write([]byte(err.Error())) - return 500, err - } - - // Create the file - var outfile *os.File - if outfile, err = os.Create(r.URL.Path + hdr.Filename); nil != err { - w.Write([]byte(err.Error())) - return 500, err - } - - // Copy the file content - if _, err = io.Copy(outfile, infile); nil != err { - w.Write([]byte(err.Error())) - return 500, err - } - } - } - - w.Header().Set("Content-Type", "application/json") - w.Write([]byte("{}")) - return 200, nil -} - -func get(w http.ResponseWriter, r *http.Request, c *config.Config) (int, error) { - functions := template.FuncMap{ - "CanBeEdited": utils.CanBeEdited, - "Defined": utils.Defined, - } - - tpl, err := utils.GetTemplate(r, functions, "browse") - - if err != nil { - w.Write([]byte(err.Error())) - return 500, err - } - - b := browse.Browse{ - Next: middleware.HandlerFunc(func(w http.ResponseWriter, r *http.Request) (int, error) { - return 404, nil - }), - Root: "./", - Configs: []browse.Config{ - { - PathScope: "/", - Variables: c, - Template: tpl, - }, - }, - IgnoreIndexes: true, - } - - return b.ServeHTTP(w, r) -} diff --git a/browse/delete.go b/browse/delete.go new file mode 100644 index 00000000..39febcb1 --- /dev/null +++ b/browse/delete.go @@ -0,0 +1,37 @@ +package browse + +import ( + "net/http" + "os" + "strings" +) + +// DELETE handles the DELETE method on browse page +func DELETE(w http.ResponseWriter, r *http.Request) (int, error) { + // Remove both beginning and trailing slashes + r.URL.Path = strings.TrimPrefix(r.URL.Path, "/") + r.URL.Path = strings.TrimSuffix(r.URL.Path, "/") + + // Check if the file or directory exists + if stat, err := os.Stat(r.URL.Path); err == nil { + var err error + // If it's dir, remove all of the content inside + if stat.IsDir() { + err = os.RemoveAll(r.URL.Path) + } else { + err = os.Remove(r.URL.Path) + } + + // Check for errors + if err != nil { + w.Write([]byte(err.Error())) + return 500, err + } + } else { + return 404, nil + } + + w.Header().Set("Content-Type", "application/json") + w.Write([]byte("{}")) + return 200, nil +} diff --git a/browse/get.go b/browse/get.go new file mode 100644 index 00000000..2e6a52ce --- /dev/null +++ b/browse/get.go @@ -0,0 +1,43 @@ +package browse + +import ( + "net/http" + "text/template" + + "github.com/hacdias/caddy-hugo/config" + "github.com/hacdias/caddy-hugo/utils" + "github.com/mholt/caddy/middleware" + "github.com/mholt/caddy/middleware/browse" +) + +// GET handles the GET method on browse page +func GET(w http.ResponseWriter, r *http.Request, c *config.Config) (int, error) { + functions := template.FuncMap{ + "CanBeEdited": utils.CanBeEdited, + "Defined": utils.Defined, + } + + tpl, err := utils.GetTemplate(r, functions, "browse") + + if err != nil { + w.Write([]byte(err.Error())) + return 500, err + } + + b := browse.Browse{ + Next: middleware.HandlerFunc(func(w http.ResponseWriter, r *http.Request) (int, error) { + return 404, nil + }), + Root: "./", + Configs: []browse.Config{ + { + PathScope: "/", + Variables: c, + Template: tpl, + }, + }, + IgnoreIndexes: true, + } + + return b.ServeHTTP(w, r) +} diff --git a/browse/post.go b/browse/post.go new file mode 100644 index 00000000..8651b911 --- /dev/null +++ b/browse/post.go @@ -0,0 +1,140 @@ +package browse + +import ( + "bytes" + "encoding/json" + "errors" + "io" + "mime/multipart" + "net/http" + "os" + "strings" + + "github.com/hacdias/caddy-hugo/utils" +) + +// POST handles the POST method on browse page +func POST(w http.ResponseWriter, r *http.Request) (int, error) { + // Remove both beginning slashes + r.URL.Path = strings.TrimPrefix(r.URL.Path, "/") + + // If it's the upload of a file + if r.Header.Get("X-Upload") == "true" { + return upload(w, r) + } + + // Get the JSON information sent using a buffer + buffer := new(bytes.Buffer) + buffer.ReadFrom(r.Body) + + // Creates the raw file "map" using the JSON + var info map[string]interface{} + json.Unmarshal(buffer.Bytes(), &info) + + // Check if filename and archetype are specified in + // the request + if _, ok := info["filename"]; !ok { + return 400, errors.New("Filename not specified.") + } + + if _, ok := info["archetype"]; !ok { + return 400, errors.New("Archtype not specified.") + } + + // Sanitize the file name path + filename := info["filename"].(string) + filename = strings.TrimPrefix(filename, "/") + filename = strings.TrimSuffix(filename, "/") + filename = r.URL.Path + filename + + // Check if the archetype is defined + if info["archetype"] != "" { + // Sanitize the archetype path + archetype := info["archetype"].(string) + archetype = strings.Replace(archetype, "/archetypes", "", 1) + archetype = strings.Replace(archetype, "archetypes", "", 1) + archetype = strings.TrimPrefix(archetype, "/") + archetype = strings.TrimSuffix(archetype, "/") + archetype = "archetypes/" + archetype + + // Check if the archetype ending with .markdown exists + if _, err := os.Stat(archetype + ".markdown"); err == nil { + err = utils.CopyFile(archetype+".markdown", filename) + if err != nil { + w.Write([]byte(err.Error())) + return 500, err + } + + w.Header().Set("Location", "/admin/edit/"+filename) + w.Header().Set("Content-Type", "application/json") + w.Write([]byte("{}")) + return 201, nil + } + + // Check if the archetype ending with .md exists + if _, err := os.Stat(archetype + ".md"); err == nil { + err = utils.CopyFile(archetype+".md", filename) + if err != nil { + w.Write([]byte(err.Error())) + return 500, err + } + + w.Header().Set("Location", "/admin/edit/"+filename) + w.Header().Set("Content-Type", "application/json") + w.Write([]byte("{}")) + return 201, nil + } + } + + wf, err := os.Create(filename) + if err != nil { + w.Write([]byte(err.Error())) + return 500, err + } + + defer wf.Close() + + w.Header().Set("Location", "/admin/edit/"+filename) + w.Header().Set("Content-Type", "application/json") + w.Write([]byte("{}")) + return 200, nil +} + +func upload(w http.ResponseWriter, r *http.Request) (int, error) { + // Parse the multipart form in the request + err := r.ParseMultipartForm(100000) + if err != nil { + w.Write([]byte(err.Error())) + return 500, err + } + + // For each file header in the multipart form + for _, fheaders := range r.MultipartForm.File { + // Handle each file + for _, hdr := range fheaders { + // Open the first file + var infile multipart.File + if infile, err = hdr.Open(); nil != err { + w.Write([]byte(err.Error())) + return 500, err + } + + // Create the file + var outfile *os.File + if outfile, err = os.Create(r.URL.Path + hdr.Filename); nil != err { + w.Write([]byte(err.Error())) + return 500, err + } + + // Copy the file content + if _, err = io.Copy(outfile, infile); nil != err { + w.Write([]byte(err.Error())) + return 500, err + } + } + } + + w.Header().Set("Content-Type", "application/json") + w.Write([]byte("{}")) + return 200, nil +} diff --git a/editor/editor.go b/editor/editor.go index 896ac358..c2c8dd4e 100644 --- a/editor/editor.go +++ b/editor/editor.go @@ -1,22 +1,10 @@ package editor import ( - "bytes" - "encoding/json" - "errors" - "io/ioutil" "net/http" - "os" - "path/filepath" "strings" - "text/template" - "time" "github.com/hacdias/caddy-hugo/config" - "github.com/hacdias/caddy-hugo/frontmatter" - "github.com/hacdias/caddy-hugo/utils" - "github.com/robfig/cron" - "github.com/spf13/hugo/parser" ) type editor struct { @@ -33,279 +21,12 @@ type editor struct { func ServeHTTP(w http.ResponseWriter, r *http.Request, c *config.Config) (int, error) { filename := strings.Replace(r.URL.Path, "/admin/edit/", "", 1) - if r.Method == "POST" { - return servePost(w, r, c, filename) - } - - return serveGet(w, r, c, filename) -} - -func servePost(w http.ResponseWriter, r *http.Request, c *config.Config, filename string) (int, error) { - // Get the JSON information sent using a buffer - rawBuffer := new(bytes.Buffer) - rawBuffer.ReadFrom(r.Body) - - // Creates the raw file "map" using the JSON - var rawFile map[string]interface{} - json.Unmarshal(rawBuffer.Bytes(), &rawFile) - - // Initializes the file content to write - var file []byte - - switch r.Header.Get("X-Content-Type") { - case "frontmatter-only": - frontmatter := strings.TrimPrefix(filepath.Ext(filename), ".") - var mark rune - - switch frontmatter { - case "toml": - mark = rune('+') - case "json": - mark = rune('{') - case "yaml": - mark = rune('-') - default: - return 400, nil - } - - f, err := parser.InterfaceToFrontMatter(rawFile, mark) - fString := string(f) - - // If it's toml or yaml, strip frontmatter identifier - if frontmatter == "toml" { - fString = strings.TrimSuffix(fString, "+++\n") - fString = strings.TrimPrefix(fString, "+++\n") - } - - if frontmatter == "yaml" { - fString = strings.TrimSuffix(fString, "---\n") - fString = strings.TrimPrefix(fString, "---\n") - } - - f = []byte(fString) - - if err != nil { - w.Write([]byte(err.Error())) - return 500, err - } - - file = f - case "content-only": - // The main content of the file - mainContent := rawFile["content"].(string) - mainContent = "\n\n" + strings.TrimSpace(mainContent) - - file = []byte(mainContent) - case "complete": - // The main content of the file - mainContent := rawFile["content"].(string) - mainContent = "\n\n" + strings.TrimSpace(mainContent) - - // Removes the main content from the rest of the frontmatter - delete(rawFile, "content") - - // Schedule the post - if r.Header.Get("X-Schedule") == "true" { - t, err := time.Parse("2006-01-02 15:04:05-07:00", rawFile["date"].(string)) - - if err != nil { - w.Write([]byte(err.Error())) - return 500, err - } - - scheduler := cron.New() - scheduler.AddFunc(t.In(time.Now().Location()).Format("05 04 15 02 01 *"), func() { - // Set draft to false - rawFile["draft"] = false - - // Converts the frontmatter in JSON - jsonFrontmatter, err := json.Marshal(rawFile) - - if err != nil { - return - } - - // Indents the json - frontMatterBuffer := new(bytes.Buffer) - json.Indent(frontMatterBuffer, jsonFrontmatter, "", " ") - - // Generates the final file - f := new(bytes.Buffer) - f.Write(frontMatterBuffer.Bytes()) - f.Write([]byte(mainContent)) - file = f.Bytes() - - // Write the file - err = ioutil.WriteFile(filename, file, 0666) - - if err != nil { - return - } - - utils.RunHugo(c) - }) - scheduler.Start() - } - - // Converts the frontmatter in JSON - jsonFrontmatter, err := json.Marshal(rawFile) - - if err != nil { - w.Write([]byte(err.Error())) - return 500, err - } - - // Indents the json - frontMatterBuffer := new(bytes.Buffer) - json.Indent(frontMatterBuffer, jsonFrontmatter, "", " ") - - // Generates the final file - f := new(bytes.Buffer) - f.Write(frontMatterBuffer.Bytes()) - f.Write([]byte(mainContent)) - file = f.Bytes() + switch r.Method { + case "POST": + return POST(w, r, c, filename) + case "GET": + return GET(w, r, c, filename) default: return 400, nil } - - // Write the file - err := ioutil.WriteFile(filename, file, 0666) - - if err != nil { - w.Write([]byte(err.Error())) - return 500, err - } - - w.Header().Set("Content-Type", "application/json") - w.Write([]byte("{}")) - return 200, nil -} - -func serveGet(w http.ResponseWriter, r *http.Request, c *config.Config, filename string) (int, error) { - // Check if the file format is supported. If not, send a "Not Acceptable" - // header and an error - if !utils.CanBeEdited(filename) { - return 406, errors.New("File format not supported.") - } - - // Check if the file exists. If it doesn't, send a "Not Found" message - if _, err := os.Stat(filename); os.IsNotExist(err) { - w.Write([]byte(err.Error())) - return 404, nil - } - - // Open the file and check if there was some error while opening - file, err := ioutil.ReadFile(filename) - if err != nil { - w.Write([]byte(err.Error())) - return 500, err - } - - // Create a new editor variable and set the extension - page := new(editor) - page.Mode = strings.TrimPrefix(filepath.Ext(filename), ".") - page.Name = filename - page.Config = c - page.IsPost = false - - // Sanitize the extension - page.Mode = sanitizeMode(page.Mode) - - // Handle the content depending on the file extension - switch page.Mode { - case "markdown": - if hasFrontMatterRune(file) { - // Starts a new buffer and parses the file using Hugo's functions - buffer := bytes.NewBuffer(file) - file, err := parser.ReadFrom(buffer) - if err != nil { - w.Write([]byte(err.Error())) - return 500, err - } - - if strings.Contains(string(file.FrontMatter()), "date") { - page.IsPost = true - } - - // Parses the page content and the frontmatter - page.Content = strings.TrimSpace(string(file.Content())) - page.FrontMatter, err = frontmatter.Pretty(file.FrontMatter()) - page.Class = "complete" - } else { - // The editor will handle only content - page.Class = "content-only" - page.Content = string(file) - } - case "json", "toml", "yaml": - // Defines the class and declares an error - page.Class = "frontmatter-only" - var err error - - // Checks if the file already has the frontmatter rune and parses it - if hasFrontMatterRune(file) { - page.FrontMatter, err = frontmatter.Pretty(file) - } else { - page.FrontMatter, err = frontmatter.Pretty(appendFrontMatterRune(file, page.Mode)) - } - - // Check if there were any errors - if err != nil { - w.Write([]byte(err.Error())) - return 500, err - } - default: - // The editor will handle only content - page.Class = "content-only" - page.Content = string(file) - } - - // Create the functions map, then the template, check for erros and - // execute the template if there aren't errors - functions := template.FuncMap{ - "SplitCapitalize": utils.SplitCapitalize, - "Defined": utils.Defined, - } - - tpl, err := utils.GetTemplate(r, functions, "editor", "frontmatter") - - if err != nil { - w.Write([]byte(err.Error())) - return 500, err - } - - return 200, tpl.Execute(w, page) -} - -func hasFrontMatterRune(file []byte) bool { - return strings.HasPrefix(string(file), "---") || - strings.HasPrefix(string(file), "+++") || - strings.HasPrefix(string(file), "{") -} - -func appendFrontMatterRune(frontmatter []byte, language string) []byte { - switch language { - case "yaml": - return []byte("---\n" + string(frontmatter) + "\n---") - case "toml": - return []byte("+++\n" + string(frontmatter) + "\n+++") - case "json": - return frontmatter - } - - return frontmatter -} - -func sanitizeMode(extension string) string { - switch extension { - case "markdown", "md": - return "markdown" - case "css", "scss": - return "css" - case "html": - return "htmlmixed" - case "js": - return "javascript" - default: - return extension - } } diff --git a/editor/get.go b/editor/get.go new file mode 100644 index 00000000..b4ae5673 --- /dev/null +++ b/editor/get.go @@ -0,0 +1,147 @@ +package editor + +import ( + "bytes" + "errors" + "io/ioutil" + "net/http" + "os" + "path/filepath" + "strings" + "text/template" + + "github.com/hacdias/caddy-hugo/config" + "github.com/hacdias/caddy-hugo/frontmatter" + "github.com/hacdias/caddy-hugo/utils" + "github.com/spf13/hugo/parser" +) + +// GET handles the GET method on editor page +func GET(w http.ResponseWriter, r *http.Request, c *config.Config, filename string) (int, error) { + // Check if the file format is supported. If not, send a "Not Acceptable" + // header and an error + if !utils.CanBeEdited(filename) { + return 406, errors.New("File format not supported.") + } + + // Check if the file exists. If it doesn't, send a "Not Found" message + if _, err := os.Stat(filename); os.IsNotExist(err) { + w.Write([]byte(err.Error())) + return 404, nil + } + + // Open the file and check if there was some error while opening + file, err := ioutil.ReadFile(filename) + if err != nil { + w.Write([]byte(err.Error())) + return 500, err + } + + // Create a new editor variable and set the extension + page := new(editor) + page.Mode = strings.TrimPrefix(filepath.Ext(filename), ".") + page.Name = filename + page.Config = c + page.IsPost = false + + // Sanitize the extension + page.Mode = sanitizeMode(page.Mode) + + // Handle the content depending on the file extension + switch page.Mode { + case "markdown": + if hasFrontMatterRune(file) { + // Starts a new buffer and parses the file using Hugo's functions + buffer := bytes.NewBuffer(file) + file, err := parser.ReadFrom(buffer) + if err != nil { + w.Write([]byte(err.Error())) + return 500, err + } + + if strings.Contains(string(file.FrontMatter()), "date") { + page.IsPost = true + } + + // Parses the page content and the frontmatter + page.Content = strings.TrimSpace(string(file.Content())) + page.FrontMatter, err = frontmatter.Pretty(file.FrontMatter()) + page.Class = "complete" + } else { + // The editor will handle only content + page.Class = "content-only" + page.Content = string(file) + } + case "json", "toml", "yaml": + // Defines the class and declares an error + page.Class = "frontmatter-only" + var err error + + // Checks if the file already has the frontmatter rune and parses it + if hasFrontMatterRune(file) { + page.FrontMatter, err = frontmatter.Pretty(file) + } else { + page.FrontMatter, err = frontmatter.Pretty(appendFrontMatterRune(file, page.Mode)) + } + + // Check if there were any errors + if err != nil { + w.Write([]byte(err.Error())) + return 500, err + } + default: + // The editor will handle only content + page.Class = "content-only" + page.Content = string(file) + } + + // Create the functions map, then the template, check for erros and + // execute the template if there aren't errors + functions := template.FuncMap{ + "SplitCapitalize": utils.SplitCapitalize, + "Defined": utils.Defined, + } + + tpl, err := utils.GetTemplate(r, functions, "editor", "frontmatter") + + if err != nil { + w.Write([]byte(err.Error())) + return 500, err + } + + return 200, tpl.Execute(w, page) +} + +func hasFrontMatterRune(file []byte) bool { + return strings.HasPrefix(string(file), "---") || + strings.HasPrefix(string(file), "+++") || + strings.HasPrefix(string(file), "{") +} + +func appendFrontMatterRune(frontmatter []byte, language string) []byte { + switch language { + case "yaml": + return []byte("---\n" + string(frontmatter) + "\n---") + case "toml": + return []byte("+++\n" + string(frontmatter) + "\n+++") + case "json": + return frontmatter + } + + return frontmatter +} + +func sanitizeMode(extension string) string { + switch extension { + case "markdown", "md": + return "markdown" + case "css", "scss": + return "css" + case "html": + return "htmlmixed" + case "js": + return "javascript" + default: + return extension + } +} diff --git a/editor/post.go b/editor/post.go new file mode 100644 index 00000000..007c22cc --- /dev/null +++ b/editor/post.go @@ -0,0 +1,175 @@ +package editor + +import ( + "bytes" + "encoding/json" + "io/ioutil" + "net/http" + "path/filepath" + "strings" + "time" + + "github.com/hacdias/caddy-hugo/config" + "github.com/hacdias/caddy-hugo/utils" + "github.com/robfig/cron" + "github.com/spf13/hugo/parser" +) + +// POST handles the POST method on editor page +func POST(w http.ResponseWriter, r *http.Request, c *config.Config, filename string) (int, error) { + // Get the JSON information sent using a buffer + rawBuffer := new(bytes.Buffer) + rawBuffer.ReadFrom(r.Body) + + // Creates the raw file "map" using the JSON + var rawFile map[string]interface{} + json.Unmarshal(rawBuffer.Bytes(), &rawFile) + + // Initializes the file content to write + var file []byte + + switch r.Header.Get("X-Content-Type") { + case "frontmatter-only": + f, code, err := parseFrontMatterOnlyFile(rawFile, filename) + if err != nil { + w.Write([]byte(err.Error())) + return code, err + } + + file = f + case "content-only": + // The main content of the file + mainContent := rawFile["content"].(string) + mainContent = "\n\n" + strings.TrimSpace(mainContent) + + file = []byte(mainContent) + case "complete": + f, code, err := parseCompleteFile(r, c, rawFile, filename) + if err != nil { + w.Write([]byte(err.Error())) + return code, err + } + + file = f + default: + return 400, nil + } + + // Write the file + err := ioutil.WriteFile(filename, file, 0666) + + if err != nil { + w.Write([]byte(err.Error())) + return 500, err + } + + w.Header().Set("Content-Type", "application/json") + w.Write([]byte("{}")) + return 200, nil +} + +func parseFrontMatterOnlyFile(rawFile map[string]interface{}, filename string) ([]byte, int, error) { + frontmatter := strings.TrimPrefix(filepath.Ext(filename), ".") + var mark rune + + switch frontmatter { + case "toml": + mark = rune('+') + case "json": + mark = rune('{') + case "yaml": + mark = rune('-') + default: + return []byte{}, 400, nil + } + + f, err := parser.InterfaceToFrontMatter(rawFile, mark) + fString := string(f) + + // If it's toml or yaml, strip frontmatter identifier + if frontmatter == "toml" { + fString = strings.TrimSuffix(fString, "+++\n") + fString = strings.TrimPrefix(fString, "+++\n") + } + + if frontmatter == "yaml" { + fString = strings.TrimSuffix(fString, "---\n") + fString = strings.TrimPrefix(fString, "---\n") + } + + f = []byte(fString) + + if err != nil { + return []byte{}, 500, err + } + + return f, 200, nil +} + +func parseCompleteFile(r *http.Request, c *config.Config, rawFile map[string]interface{}, filename string) ([]byte, int, error) { + // The main content of the file + mainContent := rawFile["content"].(string) + mainContent = "\n\n" + strings.TrimSpace(mainContent) + + // Removes the main content from the rest of the frontmatter + delete(rawFile, "content") + + // Schedule the post + if r.Header.Get("X-Schedule") == "true" { + t, err := time.Parse("2006-01-02 15:04:05-07:00", rawFile["date"].(string)) + + if err != nil { + return []byte{}, 500, err + } + + scheduler := cron.New() + scheduler.AddFunc(t.In(time.Now().Location()).Format("05 04 15 02 01 *"), func() { + // Set draft to false + rawFile["draft"] = false + + // Converts the frontmatter in JSON + jsonFrontmatter, err := json.Marshal(rawFile) + + if err != nil { + return + } + + // Indents the json + frontMatterBuffer := new(bytes.Buffer) + json.Indent(frontMatterBuffer, jsonFrontmatter, "", " ") + + // Generates the final file + f := new(bytes.Buffer) + f.Write(frontMatterBuffer.Bytes()) + f.Write([]byte(mainContent)) + file := f.Bytes() + + // Write the file + err = ioutil.WriteFile(filename, file, 0666) + + if err != nil { + return + } + + utils.RunHugo(c) + }) + scheduler.Start() + } + + // Converts the frontmatter in JSON + jsonFrontmatter, err := json.Marshal(rawFile) + + if err != nil { + return []byte{}, 500, err + } + + // Indents the json + frontMatterBuffer := new(bytes.Buffer) + json.Indent(frontMatterBuffer, jsonFrontmatter, "", " ") + + // Generates the final file + f := new(bytes.Buffer) + f.Write(frontMatterBuffer.Bytes()) + f.Write([]byte(mainContent)) + return f.Bytes(), 200, nil +} diff --git a/templates/editor.tmpl b/templates/editor.tmpl index f257fb86..1fd739c0 100644 --- a/templates/editor.tmpl +++ b/templates/editor.tmpl @@ -51,7 +51,7 @@ {{ if and (eq .Class "complete") ( .IsPost ) }} - + {{ end }}