diff --git a/api.go b/api.go
index e6bdd99..7b834f6 100644
--- a/api.go
+++ b/api.go
@@ -6,25 +6,6 @@ import (
"strings"
)
-func apiPostCreate(w http.ResponseWriter, r *http.Request) {
- defer func() {
- _ = r.Body.Close()
- }()
- post := &Post{}
- err := json.NewDecoder(r.Body).Decode(post)
- if err != nil {
- http.Error(w, err.Error(), http.StatusBadRequest)
- return
- }
- err = post.createOrReplace(false)
- if err != nil {
- http.Error(w, err.Error(), http.StatusBadRequest)
- return
- }
- w.Header().Set("Location", appConfig.Server.PublicAddress+post.Path)
- w.WriteHeader(http.StatusCreated)
-}
-
func apiPostCreateHugo(w http.ResponseWriter, r *http.Request) {
blog := r.URL.Query().Get("blog")
path := r.URL.Query().Get("path")
@@ -49,7 +30,7 @@ func apiPostCreateHugo(w http.ResponseWriter, r *http.Request) {
post.Section = section
post.Slug = slug
aliases = append(aliases, alias)
- err = post.createOrReplace(false)
+ err = post.replace()
if err != nil {
http.Error(w, err.Error(), http.StatusBadRequest)
return
@@ -67,40 +48,3 @@ func apiPostCreateHugo(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Location", appConfig.Server.PublicAddress+post.Path)
w.WriteHeader(http.StatusCreated)
}
-
-func apiPostRead(w http.ResponseWriter, r *http.Request) {
- path := r.URL.Query().Get("path")
- if path == "" {
- http.Error(w, "No path defined", http.StatusBadRequest)
- return
- }
- post, err := getPost(r.Context(), path)
- if err != nil {
- http.Error(w, err.Error(), http.StatusBadRequest)
- return
- }
- w.Header().Set(contentType, contentTypeJSONUTF8)
- err = json.NewEncoder(w).Encode(post)
- if err != nil {
- http.Error(w, err.Error(), http.StatusInternalServerError)
- return
- }
-}
-
-func apiPostDelete(w http.ResponseWriter, r *http.Request) {
- defer func() {
- _ = r.Body.Close()
- }()
- post := &Post{}
- err := json.NewDecoder(r.Body).Decode(post)
- if err != nil {
- http.Error(w, err.Error(), http.StatusBadRequest)
- return
- }
- err = post.delete()
- if err != nil {
- http.Error(w, err.Error(), http.StatusBadRequest)
- return
- }
- w.WriteHeader(http.StatusOK)
-}
diff --git a/config.go b/config.go
index 74720c4..fe7eff9 100644
--- a/config.go
+++ b/config.go
@@ -117,16 +117,13 @@ type frontmatter struct {
}
type configMicropub struct {
- Enabled bool `mapstructure:"enabled"`
- Path string `mapstructure:"path"`
- AuthAllowed []string `mapstructure:"authAllowed"`
- CategoryParam string `mapstructure:"categoryParam"`
- ReplyParam string `mapstructure:"replyParam"`
- LikeParam string `mapstructure:"likeParam"`
- BookmarkParam string `mapstructure:"bookmarkParam"`
- AudioParam string `mapstructure:"audioParam"`
- PhotoParam string `mapstructure:"photoParam"`
- PhotoDescriptionParam string `mapstructure:"photoDescriptionParam"`
+ CategoryParam string `mapstructure:"categoryParam"`
+ ReplyParam string `mapstructure:"replyParam"`
+ LikeParam string `mapstructure:"likeParam"`
+ BookmarkParam string `mapstructure:"bookmarkParam"`
+ AudioParam string `mapstructure:"audioParam"`
+ PhotoParam string `mapstructure:"photoParam"`
+ PhotoDescriptionParam string `mapstructure:"photoDescriptionParam"`
}
var appConfig = &config{}
@@ -138,6 +135,7 @@ func initConfig() error {
if err != nil {
return err
}
+ // Defaults
viper.SetDefault("server.logging", false)
viper.SetDefault("server.debug", false)
viper.SetDefault("server.port", 8080)
@@ -152,8 +150,6 @@ func initConfig() error {
viper.SetDefault("user.password", "secret")
viper.SetDefault("hooks.shell", "/bin/bash")
viper.SetDefault("hugo.frontmatter", []*frontmatter{{Meta: "title", Parameter: "title"}, {Meta: "tags", Parameter: "tags"}})
- viper.SetDefault("micropub.enabled", true)
- viper.SetDefault("micropub.path", "/micropub")
viper.SetDefault("micropub.categoryParam", "tags")
viper.SetDefault("micropub.replyParam", "replylink")
viper.SetDefault("micropub.likeParam", "likelink")
@@ -167,14 +163,14 @@ func initConfig() error {
return err
}
// Check config
+ if appConfig.Server.Domain == "" {
+ return errors.New("no domain configured")
+ }
if len(appConfig.Blogs) == 0 {
return errors.New("no blog configured")
}
if len(appConfig.DefaultBlog) == 0 || appConfig.Blogs[appConfig.DefaultBlog] == nil {
return errors.New("no default blog or default blog not present")
}
- if len(appConfig.Micropub.AuthAllowed) == 0 {
- appConfig.Micropub.AuthAllowed = []string{appConfig.Server.Domain}
- }
return nil
}
diff --git a/example-config.yaml b/example-config.yaml
index acdd7aa..9ac183d 100644
--- a/example-config.yaml
+++ b/example-config.yaml
@@ -54,10 +54,6 @@ hugo:
- meta: tags
parameter: tags
micropub:
- enabled: true
- path: /micropub
- authAllowed:
- - example.com
categoryParam: tags
replyParam: replylink
likeParam: likelink
diff --git a/http.go b/http.go
index 44164fd..85e82fc 100644
--- a/http.go
+++ b/http.go
@@ -76,22 +76,21 @@ func buildHandler() (http.Handler, error) {
// API
r.Route("/api", func(apiRouter chi.Router) {
- apiRouter.Use(authMiddleware)
- apiRouter.Post("/post", apiPostCreate)
- apiRouter.Get("/post", apiPostRead)
- apiRouter.Delete("/post", apiPostDelete)
+ apiRouter.Use(middleware.NoCache, authMiddleware)
apiRouter.Post("/hugo", apiPostCreateHugo)
})
// Micropub
- if appConfig.Micropub.Enabled {
- r.Get(appConfig.Micropub.Path, serveMicropubQuery)
- r.With(checkIndieAuth).Post(appConfig.Micropub.Path, serveMicropubPost)
- r.With(checkIndieAuth).Post(appConfig.Micropub.Path+micropubMediaSubPath, serveMicropubMedia)
- }
+ r.Route(micropubPath, func(mpRouter chi.Router) {
+ mpRouter.Use(middleware.NoCache, checkIndieAuth)
+ mpRouter.Get("/", serveMicropubQuery)
+ mpRouter.Post("/", serveMicropubPost)
+ mpRouter.Post(micropubMediaSubPath, serveMicropubMedia)
+ })
// IndieAuth
r.Route("/indieauth", func(indieauthRouter chi.Router) {
+ indieauthRouter.Use(middleware.NoCache)
indieauthRouter.With(authMiddleware).Get("/", indieAuthAuth)
indieauthRouter.With(authMiddleware).Post("/accept", indieAuthAccept)
indieauthRouter.Post("/", indieAuthAuth)
diff --git a/indieauth.go b/indieauth.go
index 1b4c516..08c5741 100644
--- a/indieauth.go
+++ b/indieauth.go
@@ -1,6 +1,7 @@
package main
import (
+ "context"
"fmt"
"net/http"
"net/url"
@@ -18,18 +19,12 @@ func checkIndieAuth(next http.Handler) http.Handler {
http.Error(w, err.Error(), http.StatusUnauthorized)
return
}
- authorized := false
- for _, allowed := range appConfig.Micropub.AuthAllowed {
- if err := compareHostnames(tokenData.Me, allowed); err == nil {
- authorized = true
- break
- }
- }
- if !authorized {
+ if err := compareHostnames(tokenData.Me, appConfig.Server.Domain); err != nil {
http.Error(w, "Forbidden", http.StatusUnauthorized)
return
}
- next.ServeHTTP(w, r)
+ ctx := context.WithValue(r.Context(), "scope", strings.Join(tokenData.Scopes, " "))
+ next.ServeHTTP(w, r.WithContext(ctx))
return
})
}
diff --git a/micropub.go b/micropub.go
index 5f418de..83d24f2 100644
--- a/micropub.go
+++ b/micropub.go
@@ -2,13 +2,19 @@ package main
import (
"errors"
+ "fmt"
"net/http"
+ "net/url"
+ "reflect"
+ "regexp"
"strings"
+ "time"
"github.com/spf13/cast"
"gopkg.in/yaml.v3"
)
+const micropubPath = "/micropub"
const micropubMediaSubPath = "/media"
type micropubConfig struct {
@@ -16,27 +22,91 @@ type micropubConfig struct {
}
func serveMicropubQuery(w http.ResponseWriter, r *http.Request) {
- if q := r.URL.Query().Get("q"); q == "config" {
+ switch r.URL.Query().Get("q") {
+ case "config":
w.Header().Add(contentType, contentTypeJSON)
w.WriteHeader(http.StatusOK)
_ = json.NewEncoder(w).Encode(µpubConfig{
// TODO: Uncomment when media endpoint implemented
// MediaEndpoint: appConfig.Server.PublicAddress + micropubMediaPath,
})
- } else {
+ case "source":
+ var mf interface{}
+ if urlString := r.URL.Query().Get("url"); urlString != "" {
+ u, err := url.Parse(r.URL.Query().Get("url"))
+ if err != nil {
+ http.Error(w, err.Error(), http.StatusBadRequest)
+ return
+ }
+ post, err := getPost(r.Context(), u.Path)
+ if err != nil {
+ http.Error(w, err.Error(), http.StatusBadRequest)
+ return
+ }
+ mf = post.toMfItem()
+ } else {
+ posts, err := getPosts(r.Context(), &postsRequestConfig{})
+ if err != nil {
+ http.Error(w, err.Error(), http.StatusInternalServerError)
+ return
+ }
+ list := map[string][]*microformatItem{}
+ for _, post := range posts {
+ list["items"] = append(list["items"], post.toMfItem())
+ }
+ mf = list
+ }
w.Header().Add(contentType, contentTypeJSON)
w.WriteHeader(http.StatusOK)
- _, _ = w.Write([]byte("{}"))
+ _ = json.NewEncoder(w).Encode(mf)
+ default:
+ w.WriteHeader(http.StatusNotFound)
+ }
+}
+
+func (post *Post) toMfItem() *microformatItem {
+ params := post.Parameters
+ params["path"] = []string{post.Path}
+ params["section"] = []string{post.Section}
+ params["blog"] = []string{post.Blog}
+ pb, _ := yaml.Marshal(post.Parameters)
+ content := fmt.Sprintf("---\n%s---\n%s", string(pb), post.Content)
+ return µformatItem{
+ Type: []string{"h-entry"},
+ Properties: µformatProperties{
+ Name: post.Parameters["title"],
+ Published: []string{post.Published},
+ Updated: []string{post.Updated},
+ Content: []string{content},
+ MpSlug: []string{post.Slug},
+ Category: post.Parameters[appConfig.Micropub.CategoryParam],
+ },
}
}
func serveMicropubPost(w http.ResponseWriter, r *http.Request) {
defer r.Body.Close()
var post *Post
- if ct := r.Header.Get(contentType); strings.Contains(ct, contentTypeWWWForm) {
- err := r.ParseForm()
- if err != nil {
- http.Error(w, err.Error(), http.StatusInternalServerError)
+ if ct := r.Header.Get(contentType); strings.Contains(ct, contentTypeWWWForm) || strings.Contains(ct, contentTypeMultipartForm) {
+ var err error
+ r.ParseForm()
+ if strings.Contains(ct, contentTypeMultipartForm) {
+ err := r.ParseMultipartForm(0)
+ if err != nil {
+ http.Error(w, err.Error(), http.StatusInternalServerError)
+ return
+ }
+ }
+ if action := micropubAction(r.Form.Get("action")); action != "" {
+ u, err := url.Parse(r.Form.Get("url"))
+ if err != nil {
+ http.Error(w, err.Error(), http.StatusBadRequest)
+ }
+ if action == actionDelete {
+ micropubDelete(w, r, u)
+ return
+ }
+ http.Error(w, "Action not supported", http.StatusNotImplemented)
return
}
post, err = convertMPValueMapToPost(r.Form)
@@ -44,17 +114,6 @@ func serveMicropubPost(w http.ResponseWriter, r *http.Request) {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
- } else if strings.Contains(ct, contentTypeMultipartForm) {
- err := r.ParseMultipartForm(1024 * 1024 * 16)
- if err != nil {
- http.Error(w, err.Error(), http.StatusInternalServerError)
- return
- }
- post, err = convertMPValueMapToPost(r.MultipartForm.Value)
- if err != nil {
- http.Error(w, err.Error(), http.StatusInternalServerError)
- return
- }
} else if strings.Contains(ct, contentTypeJSON) {
parsedMfItem := µformatItem{}
err := json.NewDecoder(r.Body).Decode(parsedMfItem)
@@ -62,6 +121,22 @@ func serveMicropubPost(w http.ResponseWriter, r *http.Request) {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
+ if parsedMfItem.Action != "" {
+ u, err := url.Parse(parsedMfItem.URL)
+ if err != nil {
+ http.Error(w, err.Error(), http.StatusBadRequest)
+ }
+ if parsedMfItem.Action == actionDelete {
+ micropubDelete(w, r, u)
+ return
+ }
+ if parsedMfItem.Action == actionUpdate {
+ micropubUpdate(w, r, u, parsedMfItem)
+ return
+ }
+ http.Error(w, "Action not supported", http.StatusNotImplemented)
+ return
+ }
post, err = convertMPMfToPost(parsedMfItem)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
@@ -71,7 +146,10 @@ func serveMicropubPost(w http.ResponseWriter, r *http.Request) {
http.Error(w, "wrong content type", http.StatusBadRequest)
return
}
- err := post.createOrReplace(true)
+ if !strings.Contains(r.Context().Value("scope").(string), "create") {
+ http.Error(w, "create scope missing", http.StatusForbidden)
+ }
+ err := post.create()
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
@@ -133,31 +211,43 @@ func convertMPValueMapToPost(values map[string][]string) (*Post, error) {
if slug, ok := values["mp-slug"]; ok {
entry.Slug = slug[0]
}
- err := computeExtraPostParameters(entry)
+ err := entry.computeExtraPostParameters()
if err != nil {
return nil, err
}
return entry, nil
}
+type micropubAction string
+
+const (
+ actionUpdate micropubAction = "update"
+ actionDelete = "delete"
+)
+
type microformatItem struct {
- Type []string `json:"type"`
- Properties *microformatProperties `json:"properties"`
+ Type []string `json:"type,omitempty"`
+ URL string `json:"url,omitempty"`
+ Action micropubAction `json:"action,omitempty"`
+ Properties *microformatProperties `json:"properties,omitempty"`
+ Replace map[string][]interface{} `json:"replace,omitempty"`
+ Add map[string][]interface{} `json:"add,omitempty"`
+ Delete interface{} `json:"delete,omitempty"`
}
type microformatProperties struct {
- Name []string `json:"name"`
- Published []string `json:"published"`
- Updated []string `json:"updated"`
- Category []string `json:"category"`
- Content []string `json:"content"`
- URL []string `json:"url"`
- InReplyTo []string `json:"in-reply-to"`
- LikeOf []string `json:"like-of"`
- BookmarkOf []string `json:"bookmark-of"`
- MpSlug []string `json:"mp-slug"`
- Photo []interface{} `json:"photo"`
- Audio []string `json:"audio"`
+ Name []string `json:"name,omitempty"`
+ Published []string `json:"published,omitempty"`
+ Updated []string `json:"updated,omitempty"`
+ Category []string `json:"category,omitempty"`
+ Content []string `json:"content,omitempty"`
+ URL []string `json:"url,omitempty"`
+ InReplyTo []string `json:"in-reply-to,omitempty"`
+ LikeOf []string `json:"like-of,omitempty"`
+ BookmarkOf []string `json:"bookmark-of,omitempty"`
+ MpSlug []string `json:"mp-slug,omitempty"`
+ Photo []interface{} `json:"photo,omitempty"`
+ Audio []string `json:"audio,omitempty"`
}
func convertMPMfToPost(mf *microformatItem) (*Post, error) {
@@ -208,7 +298,7 @@ func convertMPMfToPost(mf *microformatItem) (*Post, error) {
if len(mf.Properties.MpSlug) == 1 {
entry.Slug = mf.Properties.MpSlug[0]
}
- err := computeExtraPostParameters(entry)
+ err := entry.computeExtraPostParameters()
if err != nil {
return nil, err
}
@@ -216,22 +306,9 @@ func convertMPMfToPost(mf *microformatItem) (*Post, error) {
}
-func computeExtraPostParameters(entry *Post) error {
- // Add images not in content
- images := entry.Parameters[appConfig.Micropub.PhotoParam]
- imageAlts := entry.Parameters[appConfig.Micropub.PhotoDescriptionParam]
- useAlts := len(images) == len(imageAlts)
- for i, image := range images {
- if !strings.Contains(entry.Content, image) {
- if useAlts && len(imageAlts[i]) > 0 {
- entry.Content += "\n\n![" + imageAlts[i] + "](" + image + " \"" + imageAlts[i] + "\")"
- } else {
- entry.Content += "\n\n![](" + image + ")"
- }
- }
- }
- sep := "---\n"
- if split := strings.Split(entry.Content, sep); len(split) > 2 {
+func (post *Post) computeExtraPostParameters() error {
+ post.Content = regexp.MustCompile("\r\n").ReplaceAllString(post.Content, "\n")
+ if split := strings.Split(post.Content, "---\n"); len(split) >= 3 && len(strings.TrimSpace(split[0])) == 0 {
// Contains frontmatter
fm := split[1]
meta := map[string]interface{}{}
@@ -241,42 +318,205 @@ func computeExtraPostParameters(entry *Post) error {
}
// Find section and copy frontmatter to params
for key, value := range meta {
+ // Delete existing content - replace
+ post.Parameters[key] = []string{}
if a, ok := value.([]interface{}); ok {
for _, ae := range a {
- entry.Parameters[key] = append(entry.Parameters[key], cast.ToString(ae))
+ post.Parameters[key] = append(post.Parameters[key], cast.ToString(ae))
}
} else {
- entry.Parameters[key] = append(entry.Parameters[key], cast.ToString(value))
+ post.Parameters[key] = append(post.Parameters[key], cast.ToString(value))
}
}
// Remove frontmatter from content
- entry.Content = strings.Replace(entry.Content, split[0]+sep+split[1]+sep, "", 1)
+ post.Content = strings.Join(split[2:], "---\n")
}
// Check settings
- if blog := entry.Parameters["blog"]; len(blog) == 1 && blog[0] != "" {
- entry.Blog = blog[0]
- delete(entry.Parameters, "blog")
+ if blog := post.Parameters["blog"]; len(blog) == 1 && blog[0] != "" {
+ post.Blog = blog[0]
+ delete(post.Parameters, "blog")
} else {
- entry.Blog = appConfig.DefaultBlog
+ post.Blog = appConfig.DefaultBlog
}
- if path := entry.Parameters["path"]; len(path) == 1 && path[0] != "" {
- entry.Path = path[0]
- delete(entry.Parameters, "path")
+ if path := post.Parameters["path"]; len(path) == 1 && path[0] != "" {
+ post.Path = path[0]
+ delete(post.Parameters, "path")
}
- if section := entry.Parameters["section"]; len(section) == 1 && section[0] != "" {
- entry.Section = section[0]
- delete(entry.Parameters, "section")
+ if section := post.Parameters["section"]; len(section) == 1 && section[0] != "" {
+ post.Section = section[0]
+ delete(post.Parameters, "section")
}
- if slug := entry.Parameters["slug"]; len(slug) == 1 && slug[0] != "" {
- entry.Slug = slug[0]
- delete(entry.Parameters, "slug")
+ if slug := post.Parameters["slug"]; len(slug) == 1 && slug[0] != "" {
+ post.Slug = slug[0]
+ delete(post.Parameters, "slug")
}
- if entry.Path == "" && entry.Section == "" {
- entry.Section = appConfig.Blogs[entry.Blog].DefaultSection
+ if post.Path == "" && post.Section == "" {
+ // Has no path or section -> default section
+ post.Section = appConfig.Blogs[post.Blog].DefaultSection
+ }
+ if post.Published == "" && post.Section != "" {
+ // Has no published date, but section -> published now
+ post.Published = time.Now().String()
+ }
+ // Add images not in content
+ images := post.Parameters[appConfig.Micropub.PhotoParam]
+ imageAlts := post.Parameters[appConfig.Micropub.PhotoDescriptionParam]
+ useAlts := len(images) == len(imageAlts)
+ for i, image := range images {
+ if !strings.Contains(post.Content, image) {
+ if useAlts && len(imageAlts[i]) > 0 {
+ post.Content += "\n\n![" + imageAlts[i] + "](" + image + " \"" + imageAlts[i] + "\")"
+ } else {
+ post.Content += "\n\n![](" + image + ")"
+ }
+ }
}
return nil
}
+func micropubDelete(w http.ResponseWriter, r *http.Request, u *url.URL) {
+ if !strings.Contains(r.Context().Value("scope").(string), "delete") {
+ http.Error(w, "delete scope missing", http.StatusForbidden)
+ }
+ err := deletePost(u.Path)
+ if err != nil {
+ http.Error(w, err.Error(), http.StatusBadRequest)
+ } else {
+ w.WriteHeader(http.StatusNoContent)
+ }
+ return
+}
+
+func micropubUpdate(w http.ResponseWriter, r *http.Request, u *url.URL, mf *microformatItem) {
+ if !strings.Contains(r.Context().Value("scope").(string), "update") {
+ http.Error(w, "update scope missing", http.StatusForbidden)
+ }
+ post, err := getPost(r.Context(), u.Path)
+ if err != nil {
+ http.Error(w, err.Error(), http.StatusBadRequest)
+ return
+ }
+ if mf.Replace != nil {
+ for key, value := range mf.Replace {
+ switch key {
+ case "content":
+ post.Content = strings.TrimSpace(strings.Join(cast.ToStringSlice(value), " "))
+ case "published":
+ post.Published = strings.TrimSpace(strings.Join(cast.ToStringSlice(value), " "))
+ case "updated":
+ post.Updated = strings.TrimSpace(strings.Join(cast.ToStringSlice(value), " "))
+ case "name":
+ post.Parameters["title"] = cast.ToStringSlice(value)
+ case "category":
+ post.Parameters[appConfig.Micropub.CategoryParam] = cast.ToStringSlice(value)
+ case "in-reply-to":
+ post.Parameters[appConfig.Micropub.ReplyParam] = cast.ToStringSlice(value)
+ case "like-of":
+ post.Parameters[appConfig.Micropub.LikeParam] = cast.ToStringSlice(value)
+ case "bookmark-of":
+ post.Parameters[appConfig.Micropub.BookmarkParam] = cast.ToStringSlice(value)
+ case "audio":
+ post.Parameters[appConfig.Micropub.AudioParam] = cast.ToStringSlice(value)
+ // TODO: photo
+ }
+ }
+ }
+ if mf.Add != nil {
+ for key, value := range mf.Add {
+ switch key {
+ case "content":
+ post.Content += strings.TrimSpace(strings.Join(cast.ToStringSlice(value), " "))
+ case "published":
+ post.Published = strings.TrimSpace(strings.Join(cast.ToStringSlice(value), " "))
+ case "updated":
+ post.Updated = strings.TrimSpace(strings.Join(cast.ToStringSlice(value), " "))
+ case "category":
+ category := post.Parameters[appConfig.Micropub.CategoryParam]
+ if category == nil {
+ category = []string{}
+ }
+ post.Parameters[appConfig.Micropub.CategoryParam] = append(category, cast.ToStringSlice(value)...)
+ case "in-reply-to":
+ post.Parameters[appConfig.Micropub.ReplyParam] = cast.ToStringSlice(value)
+ case "like-of":
+ post.Parameters[appConfig.Micropub.LikeParam] = cast.ToStringSlice(value)
+ case "bookmark-of":
+ post.Parameters[appConfig.Micropub.BookmarkParam] = cast.ToStringSlice(value)
+ case "audio":
+ audio := post.Parameters[appConfig.Micropub.CategoryParam]
+ if audio == nil {
+ audio = []string{}
+ }
+ post.Parameters[appConfig.Micropub.AudioParam] = append(audio, cast.ToStringSlice(value)...)
+ // TODO: photo
+ }
+ }
+ }
+ if del := mf.Delete; del != nil {
+ if reflect.TypeOf(del).Kind() == reflect.Slice {
+ toDelete, ok := del.([]interface{})
+ if ok {
+ for _, key := range toDelete {
+ switch key {
+ case "content":
+ post.Content = ""
+ case "published":
+ post.Published = ""
+ case "updated":
+ post.Updated = ""
+ case "category":
+ delete(post.Parameters, appConfig.Micropub.CategoryParam)
+ case "in-reply-to":
+ delete(post.Parameters, appConfig.Micropub.ReplyParam)
+ case "like-of":
+ delete(post.Parameters, appConfig.Micropub.LikeParam)
+ case "bookmark-of":
+ delete(post.Parameters, appConfig.Micropub.BookmarkParam)
+ case "audio":
+ delete(post.Parameters, appConfig.Micropub.AudioParam)
+ case "photo":
+ delete(post.Parameters, appConfig.Micropub.PhotoParam)
+ delete(post.Parameters, appConfig.Micropub.PhotoDescriptionParam)
+ }
+ }
+ }
+ } else {
+ toDelete, ok := del.(map[string]interface{})
+ if ok {
+ for key := range toDelete {
+ if ok {
+ switch key {
+ case "content":
+ post.Content = ""
+ case "published":
+ post.Published = ""
+ case "updated":
+ post.Updated = ""
+ case "in-reply-to":
+ delete(post.Parameters, appConfig.Micropub.ReplyParam)
+ case "like-of":
+ delete(post.Parameters, appConfig.Micropub.LikeParam)
+ case "bookmark-of":
+ delete(post.Parameters, appConfig.Micropub.BookmarkParam)
+ // Use content to edit other parameters
+ }
+ }
+ }
+ }
+ }
+ }
+ err = post.computeExtraPostParameters()
+ if err != nil {
+ http.Error(w, err.Error(), http.StatusInternalServerError)
+ return
+ }
+ err = post.replace()
+ if err != nil {
+ http.Error(w, err.Error(), http.StatusInternalServerError)
+ return
+ }
+}
+
func serveMicropubMedia(w http.ResponseWriter, r *http.Request) {
// TODO: Implement media server
}
diff --git a/postsDb.go b/postsDb.go
index 559b09f..d55c12f 100644
--- a/postsDb.go
+++ b/postsDb.go
@@ -2,7 +2,6 @@ package main
import (
"bytes"
- "context"
"errors"
"fmt"
"strings"
@@ -12,7 +11,7 @@ import (
"github.com/araddon/dateparse"
)
-func (p *Post) checkPost(new bool) error {
+func (p *Post) checkPost() error {
if p == nil {
return errors.New("no post")
}
@@ -27,9 +26,6 @@ func (p *Post) checkPost(new bool) error {
}
p.Published = d.String()
}
- if p.Published == "" {
- p.Published = now.String()
- }
if p.Updated != "" {
d, err := dateparse.ParseIn(p.Updated, time.Local)
if err != nil {
@@ -85,18 +81,19 @@ func (p *Post) checkPost(new bool) error {
if p.Path != "" && !strings.HasPrefix(p.Path, "/") {
return errors.New("wrong path")
}
- // Check if post with path already exists
- if new {
- post, _ := getPost(context.Background(), p.Path)
- if post != nil {
- return errors.New("path already exists")
- }
- }
return nil
}
+func (p *Post) create() error {
+ return p.createOrReplace(true)
+}
+
+func (p *Post) replace() error {
+ return p.createOrReplace(false)
+}
+
func (p *Post) createOrReplace(new bool) error {
- err := p.checkPost(new)
+ err := p.checkPost()
if err != nil {
return err
}
@@ -106,7 +103,11 @@ func (p *Post) createOrReplace(new bool) error {
finishWritingToDb()
return err
}
- _, err = tx.Exec("insert or replace into posts (path, content, published, updated, blog, section) values (?, ?, ?, ?, ?, ?)", p.Path, p.Content, p.Published, p.Updated, p.Blog, p.Section)
+ sqlCommand := "insert"
+ if !new {
+ sqlCommand = "insert or replace"
+ }
+ _, err = tx.Exec(sqlCommand+" into posts (path, content, published, updated, blog, section) values (?, ?, ?, ?, ?, ?)", p.Path, p.Content, p.Published, p.Updated, p.Blog, p.Section)
if err != nil {
_ = tx.Rollback()
finishWritingToDb()
@@ -140,11 +141,9 @@ func (p *Post) createOrReplace(new bool) error {
return reloadRouter()
}
-func (p *Post) delete() error {
- // TODO
- err := p.checkPost(false)
- if err != nil {
- return err
+func deletePost(path string) error {
+ if path == "" {
+ return nil
}
startWritingToDb()
tx, err := appDb.Begin()
@@ -152,13 +151,13 @@ func (p *Post) delete() error {
finishWritingToDb()
return err
}
- _, err = tx.Exec("delete from posts where path=?", p.Path)
+ _, err = tx.Exec("delete from posts where path=?", path)
if err != nil {
_ = tx.Rollback()
finishWritingToDb()
return err
}
- _, err = tx.Exec("delete from post_parameters where path=?", p.Path)
+ _, err = tx.Exec("delete from post_parameters where path=?", path)
if err != nil {
_ = tx.Rollback()
finishWritingToDb()
diff --git a/render.go b/render.go
index 46ea2dc..8e1a843 100644
--- a/render.go
+++ b/render.go
@@ -33,9 +33,6 @@ var templateFunctions template.FuncMap
func initRendering() error {
templateFunctions = template.FuncMap{
- "micropub": func() *configMicropub {
- return appConfig.Micropub
- },
"menu": func(blog *configBlog, id string) *menu {
return blog.Menus[id]
},
@@ -94,19 +91,18 @@ func initRendering() error {
}
return path
},
- "jsonFile": func(filename string) *interface{} {
- parsed := []*interface{}{}
+ "jsonFile": func(filename string) *map[string]interface{} {
+ parsed := &map[string]interface{}{}
content, err := ioutil.ReadFile(filename)
if err != nil {
return nil
}
- contentString := "[" + string(content) + "]"
- err = json.Unmarshal([]byte(contentString), &parsed)
+ err = json.Unmarshal(content, parsed)
if err != nil {
fmt.Println(err.Error())
return nil
}
- return parsed[0]
+ return parsed
},
}
diff --git a/templates/base.gohtml b/templates/base.gohtml
index f3b561f..d8ee40b 100644
--- a/templates/base.gohtml
+++ b/templates/base.gohtml
@@ -6,9 +6,7 @@
{{ template "title" . }}
- {{ if micropub.Enabled }}
- {{ include "micropub" .Blog .Data }}
- {{ end }}
+ {{ include "micropub" .Blog .Data }}
{{ include "header" .Blog .Data }}
{{ template "main" . }}
{{ end }}
\ No newline at end of file
diff --git a/templates/micropub.gohtml b/templates/micropub.gohtml
index b47ca91..f0cca47 100644
--- a/templates/micropub.gohtml
+++ b/templates/micropub.gohtml
@@ -1,7 +1,5 @@
{{ define "micropub" }}
- {{ with micropub.Path }}
-
- {{ end }}
+
{{ end }}
\ No newline at end of file