333211d4d0
Signed-off-by: Tobias Erbshäußer <tobias@tesoft.dev>
152 lines
3.0 KiB
Go
152 lines
3.0 KiB
Go
package main
|
|
|
|
import (
|
|
"archive/tar"
|
|
"backend/md"
|
|
"encoding/json"
|
|
"errors"
|
|
"fmt"
|
|
"io"
|
|
"log"
|
|
"net/http"
|
|
"strconv"
|
|
"strings"
|
|
"time"
|
|
)
|
|
|
|
type ArticleStatus = int
|
|
|
|
const (
|
|
ArticleStatusDraft ArticleStatus = iota
|
|
ArticleStatusPublished
|
|
ArticleStatusOffline
|
|
)
|
|
|
|
type ArticleProperties struct {
|
|
Id int64 `json:"id"`
|
|
Title string `json:"title"`
|
|
Status ArticleStatus `json:"status"`
|
|
Tags []string `json:"tags"`
|
|
ReleaseDate time.Time `json:"date"`
|
|
ModificationDate *time.Time `json:"mod-date"`
|
|
}
|
|
|
|
type Article struct {
|
|
ArticleProperties
|
|
Content string
|
|
Files []ArticleFile
|
|
}
|
|
|
|
type ArticleFile struct {
|
|
Id int
|
|
Data []byte
|
|
}
|
|
|
|
func ParseArticle(reader io.Reader, filePrefix string) (*Article, error) {
|
|
tarFiles := make(map[string][]byte)
|
|
tarReader := tar.NewReader(reader)
|
|
for {
|
|
header, err := tarReader.Next()
|
|
if err == io.EOF {
|
|
break
|
|
}
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
content, err := io.ReadAll(tarReader)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
tarFiles[header.Name] = content
|
|
}
|
|
|
|
readmeBytes, found := tarFiles["README.md"]
|
|
if !found {
|
|
return nil, errors.New("README.md not found")
|
|
}
|
|
|
|
usedFiles := make(map[string]ArticleFile)
|
|
nextFileId := 0
|
|
|
|
pc, html, err := md.Parse(func(path string) (string, error) {
|
|
content, ok := tarFiles[strings.TrimPrefix(path, "./")]
|
|
if !ok {
|
|
return "", errors.New("file '" + path + "' not found")
|
|
}
|
|
|
|
usedFiles[path] = ArticleFile{nextFileId, content}
|
|
nextFileId++
|
|
|
|
return filePrefix + strconv.Itoa(nextFileId), nil
|
|
}, readmeBytes)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
title, err := md.GetProperty(pc, "title")
|
|
if err != nil {
|
|
if errors.Is(err, md.PropertyNotFoundError) {
|
|
return nil, errors.New("title property not found")
|
|
}
|
|
|
|
return nil, err
|
|
}
|
|
|
|
tagsStr, err := md.GetProperty(pc, "tags")
|
|
if err != nil && !errors.Is(err, md.PropertyNotFoundError) {
|
|
return nil, err
|
|
}
|
|
|
|
tags := make([]string, 0)
|
|
if strings.TrimSpace(tagsStr) != "" {
|
|
for _, tagStr := range strings.Split(tagsStr, ",") {
|
|
tags = append(tags, strings.TrimSpace(tagStr))
|
|
}
|
|
}
|
|
|
|
dateStr, err := md.GetProperty(pc, "date")
|
|
if err != nil {
|
|
if errors.Is(err, md.PropertyNotFoundError) {
|
|
return nil, errors.New("date property not found")
|
|
}
|
|
|
|
return nil, err
|
|
}
|
|
releaseDate, err := time.Parse(time.DateOnly, dateStr)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("invalid date property '%s': %v", dateStr, err)
|
|
}
|
|
|
|
var modificationDate *time.Time
|
|
dateStr, err = md.GetProperty(pc, "mod-date")
|
|
if err == nil {
|
|
tmp, err := time.Parse(time.DateOnly, dateStr)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("invalid mod-date property '%s': %v", dateStr, err)
|
|
}
|
|
|
|
modificationDate = &tmp
|
|
} else if !errors.Is(err, md.PropertyNotFoundError) {
|
|
return nil, err
|
|
}
|
|
|
|
files := make([]ArticleFile, 0, len(usedFiles))
|
|
for _, file := range usedFiles {
|
|
files = append(files, file)
|
|
}
|
|
|
|
return &Article{
|
|
ArticleProperties{
|
|
-1,
|
|
title,
|
|
ArticleStatusDraft,
|
|
tags,
|
|
releaseDate,
|
|
modificationDate,
|
|
},
|
|
string(html),
|
|
files,
|
|
}, nil
|
|
}
|