Files
website/backend/blog.go
T
2026-05-24 09:40:19 +02:00

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
}