2019-11-07 10:00:24 +00:00
|
|
|
package main
|
|
|
|
|
|
|
|
import (
|
2019-11-08 18:46:07 +00:00
|
|
|
"bufio"
|
|
|
|
"bytes"
|
2019-12-22 09:23:57 +00:00
|
|
|
"encoding/json"
|
2019-11-07 10:00:24 +00:00
|
|
|
"errors"
|
|
|
|
"fmt"
|
2019-11-07 11:10:45 +00:00
|
|
|
"io/ioutil"
|
2019-11-07 10:00:24 +00:00
|
|
|
"math/rand"
|
2019-11-07 11:10:45 +00:00
|
|
|
"net/http"
|
2019-11-07 10:00:24 +00:00
|
|
|
"net/url"
|
|
|
|
"strings"
|
|
|
|
"time"
|
|
|
|
)
|
|
|
|
|
|
|
|
type Entry struct {
|
2020-01-12 17:17:40 +00:00
|
|
|
content string
|
|
|
|
title string
|
|
|
|
date string
|
|
|
|
lastmod string
|
|
|
|
section string
|
|
|
|
tags []string
|
|
|
|
link string
|
|
|
|
slug string
|
|
|
|
replyLink string
|
|
|
|
replyTitle string
|
|
|
|
likeLink string
|
|
|
|
likeTitle string
|
|
|
|
syndicate []string
|
|
|
|
language string
|
|
|
|
translationKey string
|
2020-01-19 07:36:10 +00:00
|
|
|
images []string
|
|
|
|
audio string
|
2020-01-12 17:17:40 +00:00
|
|
|
filename string
|
|
|
|
location string
|
|
|
|
token string
|
2019-11-07 10:00:24 +00:00
|
|
|
}
|
|
|
|
|
2019-11-07 11:10:45 +00:00
|
|
|
func CreateEntry(contentType ContentType, r *http.Request) (*Entry, error) {
|
2019-11-07 10:00:24 +00:00
|
|
|
if contentType == WwwForm {
|
2019-11-07 11:10:45 +00:00
|
|
|
bodyString, err := parseRequestBody(r)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
bodyValues, err := url.ParseQuery(bodyString)
|
2019-11-07 10:00:24 +00:00
|
|
|
if err != nil {
|
|
|
|
return nil, errors.New("failed to parse query")
|
|
|
|
}
|
2019-11-08 18:46:07 +00:00
|
|
|
return createEntryFromValueMap(bodyValues)
|
2019-11-07 11:10:45 +00:00
|
|
|
} else if contentType == Multipart {
|
|
|
|
err := r.ParseMultipartForm(1024 * 1024 * 16)
|
|
|
|
if err != nil {
|
|
|
|
return nil, errors.New("failed to parse Multipart")
|
|
|
|
}
|
2019-11-08 18:46:07 +00:00
|
|
|
return createEntryFromValueMap(r.MultipartForm.Value)
|
2019-11-07 11:10:45 +00:00
|
|
|
} else if contentType == Json {
|
2019-12-22 07:26:38 +00:00
|
|
|
bodyString, err := parseRequestBody(r)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
parsedMfItem := &MicroformatItem{}
|
2019-12-22 09:23:57 +00:00
|
|
|
err = json.Unmarshal([]byte(bodyString), &parsedMfItem)
|
2019-12-22 07:26:38 +00:00
|
|
|
if err != nil {
|
|
|
|
return nil, errors.New("failed to parse Json")
|
|
|
|
}
|
|
|
|
return createEntryFromMicroformat(parsedMfItem)
|
2019-11-07 10:00:24 +00:00
|
|
|
} else {
|
|
|
|
return nil, errors.New("unsupported content-type")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-11-07 11:10:45 +00:00
|
|
|
func parseRequestBody(r *http.Request) (string, error) {
|
|
|
|
defer r.Body.Close()
|
|
|
|
bodyBytes, err := ioutil.ReadAll(r.Body)
|
|
|
|
if err != nil {
|
|
|
|
return "", errors.New("failed to read body")
|
|
|
|
}
|
|
|
|
return string(bodyBytes), nil
|
|
|
|
}
|
|
|
|
|
2019-11-08 18:46:07 +00:00
|
|
|
func createEntryFromValueMap(values map[string][]string) (*Entry, error) {
|
2019-12-22 07:26:38 +00:00
|
|
|
if h, ok := values["h"]; ok && (len(h) != 1 || h[0] != "entry") {
|
2019-11-07 10:00:24 +00:00
|
|
|
return nil, errors.New("only entry type is supported so far")
|
|
|
|
}
|
2020-01-12 16:30:32 +00:00
|
|
|
entry := &Entry{}
|
|
|
|
if content, ok := values["content"]; ok {
|
|
|
|
entry.content = content[0]
|
|
|
|
}
|
|
|
|
if name, ok := values["name"]; ok {
|
|
|
|
entry.title = name[0]
|
|
|
|
}
|
|
|
|
if category, ok := values["category"]; ok {
|
|
|
|
entry.tags = category
|
|
|
|
} else if categories, ok := values["category[]"]; ok {
|
|
|
|
entry.tags = categories
|
|
|
|
}
|
|
|
|
if slug, ok := values["mp-slug"]; ok && len(slug) > 0 && slug[0] != "" {
|
|
|
|
entry.slug = slug[0]
|
|
|
|
}
|
|
|
|
if inReplyTo, ok := values["in-reply-to"]; ok {
|
|
|
|
entry.replyLink = inReplyTo[0]
|
|
|
|
}
|
|
|
|
if likeOf, ok := values["like-of"]; ok {
|
|
|
|
entry.likeLink = likeOf[0]
|
|
|
|
}
|
|
|
|
if bookmarkOf, ok := values["bookmark-of"]; ok {
|
|
|
|
entry.link = bookmarkOf[0]
|
|
|
|
}
|
|
|
|
if syndicate, ok := values["mp-syndicate-to"]; ok {
|
|
|
|
entry.syndicate = syndicate
|
|
|
|
} else if syndicates, ok := values["mp-syndicate-to[]"]; ok {
|
|
|
|
entry.syndicate = syndicates
|
2020-01-19 07:36:10 +00:00
|
|
|
}
|
|
|
|
if photo, ok := values["photo"]; ok {
|
|
|
|
entry.images = photo
|
|
|
|
} else if photo, ok := values["photo[]"]; ok {
|
|
|
|
entry.images = photo
|
|
|
|
}
|
|
|
|
if audio, ok := values["audio"]; ok {
|
|
|
|
entry.audio = audio[0]
|
|
|
|
} else if audio, ok := values["audio[]"]; ok {
|
|
|
|
entry.audio = audio[0]
|
2020-01-12 16:30:32 +00:00
|
|
|
}
|
|
|
|
if token, ok := values["access_token"]; ok {
|
|
|
|
entry.token = "Bearer " + token[0]
|
2019-11-07 10:00:24 +00:00
|
|
|
}
|
2020-01-12 16:30:32 +00:00
|
|
|
err := computeExtraSettings(entry)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
return entry, nil
|
2019-11-07 10:00:24 +00:00
|
|
|
}
|
|
|
|
|
2019-12-22 07:26:38 +00:00
|
|
|
func createEntryFromMicroformat(mfEntry *MicroformatItem) (*Entry, error) {
|
|
|
|
if len(mfEntry.Type) != 1 || mfEntry.Type[0] != "h-entry" {
|
|
|
|
return nil, errors.New("only entry type is supported so far")
|
|
|
|
}
|
2020-01-12 16:30:32 +00:00
|
|
|
entry := &Entry{}
|
2019-12-22 07:26:38 +00:00
|
|
|
if mfEntry.Properties != nil && len(mfEntry.Properties.Content) == 1 && len(mfEntry.Properties.Content[0]) > 0 {
|
2020-01-12 16:30:32 +00:00
|
|
|
entry.content = mfEntry.Properties.Content[0]
|
|
|
|
}
|
|
|
|
if len(mfEntry.Properties.Name) == 1 {
|
|
|
|
entry.title = mfEntry.Properties.Name[0]
|
|
|
|
}
|
|
|
|
if len(mfEntry.Properties.Category) > 0 {
|
|
|
|
entry.tags = mfEntry.Properties.Category
|
|
|
|
}
|
|
|
|
if len(mfEntry.Properties.MpSlug) == 1 && len(mfEntry.Properties.MpSlug[0]) > 0 {
|
|
|
|
entry.slug = mfEntry.Properties.MpSlug[0]
|
|
|
|
}
|
|
|
|
if len(mfEntry.Properties.InReplyTo) == 1 {
|
|
|
|
entry.replyLink = mfEntry.Properties.InReplyTo[0]
|
|
|
|
}
|
|
|
|
if len(mfEntry.Properties.LikeOf) == 1 {
|
|
|
|
entry.likeLink = mfEntry.Properties.LikeOf[0]
|
|
|
|
}
|
|
|
|
if len(mfEntry.Properties.BookmarkOf) == 1 {
|
|
|
|
entry.link = mfEntry.Properties.BookmarkOf[0]
|
|
|
|
}
|
|
|
|
if len(mfEntry.Properties.MpSyndicateTo) > 0 {
|
|
|
|
entry.syndicate = mfEntry.Properties.MpSyndicateTo
|
|
|
|
}
|
2020-01-19 07:36:10 +00:00
|
|
|
if len(mfEntry.Properties.Photo) > 0 {
|
|
|
|
entry.images = mfEntry.Properties.Photo
|
|
|
|
}
|
|
|
|
if len(mfEntry.Properties.Audio) > 0 {
|
|
|
|
entry.audio = mfEntry.Properties.Audio[0]
|
|
|
|
}
|
2020-01-12 16:30:32 +00:00
|
|
|
err := computeExtraSettings(entry)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
2019-12-22 07:26:38 +00:00
|
|
|
}
|
2020-01-12 16:30:32 +00:00
|
|
|
return entry, nil
|
2019-12-22 07:26:38 +00:00
|
|
|
}
|
|
|
|
|
2019-11-07 10:00:24 +00:00
|
|
|
func computeExtraSettings(entry *Entry) error {
|
2019-12-06 09:32:30 +00:00
|
|
|
now := time.Now()
|
|
|
|
// Set date
|
|
|
|
entry.date = now.Format(time.RFC3339)
|
2019-11-08 18:46:07 +00:00
|
|
|
// Find settings hidden in content
|
|
|
|
var filteredContent bytes.Buffer
|
|
|
|
contentScanner := bufio.NewScanner(strings.NewReader(entry.content))
|
|
|
|
for contentScanner.Scan() {
|
|
|
|
text := contentScanner.Text()
|
|
|
|
if strings.HasPrefix(text, "section: ") {
|
|
|
|
// Section
|
|
|
|
entry.section = strings.TrimPrefix(text, "section: ")
|
2019-11-08 23:40:55 +00:00
|
|
|
} else if strings.HasPrefix(text, "title: ") {
|
|
|
|
// Title
|
|
|
|
entry.title = strings.TrimPrefix(text, "title: ")
|
2019-11-08 18:46:07 +00:00
|
|
|
} else if strings.HasPrefix(text, "slug: ") {
|
|
|
|
// Slug
|
|
|
|
entry.slug = strings.TrimPrefix(text, "slug: ")
|
2019-11-19 09:02:20 +00:00
|
|
|
} else if strings.HasPrefix(text, "tags: ") {
|
|
|
|
// Tags
|
|
|
|
entry.tags = strings.Split(strings.TrimPrefix(text, "tags: "), ",")
|
2019-11-08 18:46:07 +00:00
|
|
|
} else if strings.HasPrefix(text, "link: ") {
|
|
|
|
// Link
|
|
|
|
entry.link = strings.TrimPrefix(text, "link: ")
|
|
|
|
} else if strings.HasPrefix(text, "reply-link: ") {
|
|
|
|
// Reply link
|
|
|
|
entry.replyLink = strings.TrimPrefix(text, "reply-link: ")
|
|
|
|
} else if strings.HasPrefix(text, "reply-title: ") {
|
|
|
|
// Reply title
|
|
|
|
entry.replyTitle = strings.TrimPrefix(text, "reply-title: ")
|
2019-11-19 09:02:20 +00:00
|
|
|
} else if strings.HasPrefix(text, "like-link: ") {
|
|
|
|
// Like link
|
|
|
|
entry.likeLink = strings.TrimPrefix(text, "like-link: ")
|
|
|
|
} else if strings.HasPrefix(text, "like-title: ") {
|
|
|
|
// Like title
|
|
|
|
entry.likeTitle = strings.TrimPrefix(text, "like-title: ")
|
2020-01-12 17:17:40 +00:00
|
|
|
} else if strings.HasPrefix(text, "language: ") {
|
|
|
|
// Language
|
|
|
|
entry.language = strings.TrimPrefix(text, "language: ")
|
|
|
|
} else if strings.HasPrefix(text, "translationkey: ") {
|
|
|
|
// Translation key
|
|
|
|
entry.translationKey = strings.TrimPrefix(text, "translationkey: ")
|
2020-01-19 07:36:10 +00:00
|
|
|
} else if strings.HasPrefix(text, "images: ") {
|
|
|
|
// Images
|
|
|
|
entry.images = strings.Split(strings.TrimPrefix(text, "images: "), ",")
|
|
|
|
} else if strings.HasPrefix(text, "audio: ") {
|
|
|
|
// Audio
|
|
|
|
entry.audio = strings.TrimPrefix(text, "audio: ")
|
2019-11-07 10:00:24 +00:00
|
|
|
} else {
|
2019-11-08 18:46:07 +00:00
|
|
|
_, _ = fmt.Fprintln(&filteredContent, text)
|
2019-11-07 10:00:24 +00:00
|
|
|
}
|
|
|
|
}
|
2019-11-08 18:46:07 +00:00
|
|
|
entry.content = filteredContent.String()
|
2020-01-19 07:36:10 +00:00
|
|
|
// Check if content contains images or add them
|
|
|
|
for _, image := range entry.images {
|
|
|
|
if !strings.Contains(entry.content, image) {
|
|
|
|
entry.content += "\n![](" + image + ")\n"
|
|
|
|
}
|
|
|
|
}
|
2019-11-07 10:00:24 +00:00
|
|
|
// Compute slug if empty
|
2019-11-08 18:46:07 +00:00
|
|
|
if len(entry.slug) == 0 || entry.slug == "" {
|
2019-11-07 10:00:24 +00:00
|
|
|
random := generateRandomString(now, 5)
|
2019-11-08 18:46:07 +00:00
|
|
|
entry.slug = fmt.Sprintf("%v-%02d-%02d-%v", now.Year(), int(now.Month()), now.Day(), random)
|
2019-11-07 10:00:24 +00:00
|
|
|
}
|
|
|
|
// Compute filename and location
|
2020-01-12 17:17:40 +00:00
|
|
|
contentFolder := "content"
|
|
|
|
localizedBlogUrl := BlogUrl
|
|
|
|
if len(entry.language) > 0 && entry.language != "en" {
|
|
|
|
// Append language to content folder: "content-de" for language "de"
|
|
|
|
contentFolder += "-" + entry.language
|
|
|
|
// Append language to BlogUrl
|
|
|
|
localizedBlogUrl += entry.language + "/"
|
|
|
|
}
|
2019-12-03 16:46:16 +00:00
|
|
|
if len(entry.section) < 1 {
|
|
|
|
entry.section = "micro"
|
|
|
|
}
|
|
|
|
entry.section = strings.ToLower(entry.section)
|
2019-12-22 07:26:38 +00:00
|
|
|
if entry.section == "thoughts" || entry.section == "links" || entry.section == "micro" {
|
2020-01-12 17:17:40 +00:00
|
|
|
entry.filename = fmt.Sprintf("%v/%v/%02d/%02d/%v.md", contentFolder, entry.section, now.Year(), int(now.Month()), entry.slug)
|
|
|
|
entry.location = fmt.Sprintf("%v%v/%02d/%02d/%v/", localizedBlogUrl, entry.section, now.Year(), int(now.Month()), entry.slug)
|
2019-11-07 10:00:24 +00:00
|
|
|
} else {
|
2020-01-12 17:17:40 +00:00
|
|
|
entry.filename = fmt.Sprintf("%v/%v/%v.md", contentFolder, entry.section, entry.slug)
|
|
|
|
entry.location = fmt.Sprintf("%v%v/%v/", localizedBlogUrl, entry.section, entry.slug)
|
2019-11-07 10:00:24 +00:00
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func generateRandomString(now time.Time, n int) string {
|
|
|
|
rand.Seed(now.UnixNano())
|
|
|
|
letters := []rune("abcdefghijklmnopqrstuvwxyz")
|
|
|
|
b := make([]rune, n)
|
|
|
|
for i := range b {
|
|
|
|
b[i] = letters[rand.Intn(len(letters))]
|
|
|
|
}
|
|
|
|
return string(b)
|
|
|
|
}
|
|
|
|
|
2019-12-05 09:35:53 +00:00
|
|
|
func WriteEntry(entry *Entry) (location string, err error) {
|
|
|
|
file, err := WriteHugoPost(entry)
|
2019-11-07 10:00:24 +00:00
|
|
|
if err != nil {
|
2019-12-05 09:35:53 +00:00
|
|
|
return
|
2019-11-07 10:00:24 +00:00
|
|
|
}
|
2019-12-22 20:17:11 +00:00
|
|
|
err = SelectedStorage.CreateFile(entry.filename, file, entry.title)
|
2019-12-05 09:35:53 +00:00
|
|
|
if err != nil {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
location = entry.location
|
|
|
|
return
|
2019-11-07 10:00:24 +00:00
|
|
|
}
|
2019-11-14 17:46:49 +00:00
|
|
|
|
2020-01-12 17:17:40 +00:00
|
|
|
func analyzeURL(url string) (filePath string, section string, slug string, lang string, err error) {
|
2019-12-06 09:32:30 +00:00
|
|
|
if !strings.HasPrefix(url, BlogUrl) {
|
2019-11-14 17:46:49 +00:00
|
|
|
return
|
|
|
|
}
|
2020-01-12 17:17:40 +00:00
|
|
|
contentFolder := "content"
|
|
|
|
path := ""
|
|
|
|
if strings.HasPrefix(url, BlogUrl+"de/") {
|
|
|
|
lang = "de"
|
|
|
|
// German content folder
|
|
|
|
contentFolder += "-" + lang
|
|
|
|
path = strings.TrimSuffix(strings.TrimPrefix(url, BlogUrl+lang+"/"), "/")
|
|
|
|
} else {
|
|
|
|
path = strings.TrimSuffix(strings.TrimPrefix(url, BlogUrl), "/")
|
|
|
|
}
|
2019-11-14 17:46:49 +00:00
|
|
|
pathParts := strings.Split(path, "/")
|
2020-01-12 17:17:40 +00:00
|
|
|
filePath = contentFolder + "/" + path + ".md"
|
2019-11-14 17:46:49 +00:00
|
|
|
section = pathParts[0]
|
|
|
|
slug = pathParts[len(pathParts)-1]
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
func ReadEntry(url string) (entry *Entry, err error) {
|
2020-01-12 17:17:40 +00:00
|
|
|
filePath, section, slug, lang, err := analyzeURL(url)
|
2019-11-14 17:46:49 +00:00
|
|
|
if err != nil {
|
|
|
|
return
|
|
|
|
}
|
2019-12-22 20:17:11 +00:00
|
|
|
fileContent, _, exists, err := SelectedStorage.ReadFile(filePath)
|
|
|
|
if err != nil || !exists {
|
2020-01-12 17:26:21 +00:00
|
|
|
err = errors.New("failed to read file or entry doesn't exist")
|
2019-11-14 17:46:49 +00:00
|
|
|
return
|
|
|
|
}
|
|
|
|
entry, err = ReadHugoPost(fileContent)
|
|
|
|
if entry != nil {
|
|
|
|
entry.location = url
|
|
|
|
entry.filename = filePath
|
|
|
|
entry.section = section
|
|
|
|
entry.slug = slug
|
2020-01-12 17:17:40 +00:00
|
|
|
entry.language = lang
|
2019-11-14 17:46:49 +00:00
|
|
|
}
|
|
|
|
return
|
|
|
|
}
|