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