summaryrefslogtreecommitdiff
path: root/internal/post/post.go
blob: 38dff03438b0dee1e48e46dba155580fc6d125fb (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
// Package post defines the Post data model and its JSON serialisation format.
// Each post is stored as post.json inside its own directory under outdir/posts/.
// This allows pages to be re-generated without re-processing the original inputs.
package post

import (
	"encoding/json"
	"fmt"
	"os"
	"path/filepath"
	"time"
)

// Type enumerates the supported input content types.
type Type string

const (
	TypeText     Type = "text"
	TypeMarkdown Type = "markdown"
	TypeImage    Type = "image"
	TypeAudio    Type = "audio"
)

// Post represents a single microblog entry.
// It is persisted as post.json in outdir/posts/<ID>/.
type Post struct {
	// ID is the unique directory-name-safe timestamp, e.g. "2026-04-09-143022".
	ID string `json:"id"`

	// Timestamp is the moment the post was processed (UTC).
	Timestamp time.Time `json:"timestamp"`

	// PostType determines how the content was generated and how it should be rendered.
	PostType Type `json:"type"`

	// Content is the pre-rendered HTML snippet for this post (without outer post-card wrapper).
	Content string `json:"content"`

	// Assets lists filenames (not paths) of any asset files stored alongside post.json.
	Assets []string `json:"assets,omitempty"`
}

// Load reads and parses post.json from dir.
func Load(dir string) (*Post, error) {
	path := filepath.Join(dir, "post.json")

	data, err := os.ReadFile(path)
	if err != nil {
		return nil, fmt.Errorf("read post.json in %s: %w", dir, err)
	}

	var p Post
	if err := json.Unmarshal(data, &p); err != nil {
		return nil, fmt.Errorf("unmarshal post.json in %s: %w", dir, err)
	}

	return &p, nil
}

// NewID generates a unique post ID from the given time.
// Format: YYYY-MM-DD-HHmmss, optionally suffixed with -N for collisions.
func NewID(t time.Time, suffix int) string {
	base := t.UTC().Format("2006-01-02-150405")
	if suffix == 0 {
		return base
	}

	return fmt.Sprintf("%s-%d", base, suffix)
}

// Save writes the post as post.json into dir.
func (p *Post) Save(dir string) error {
	data, err := json.MarshalIndent(p, "", "  ")
	if err != nil {
		return fmt.Errorf("marshal post %s: %w", p.ID, err)
	}

	path := filepath.Join(dir, "post.json")
	if err := os.WriteFile(path, data, 0o644); err != nil {
		return fmt.Errorf("write post.json for %s: %w", p.ID, err)
	}

	return nil
}