summaryrefslogtreecommitdiff
path: root/srv/src/cmd/import-posts/main.go
blob: e4eb9e755d08bf82e658193a8ee6478f667430b6 (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
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
package main

import (
	"context"
	"fmt"
	"os"
	"path/filepath"
	"regexp"
	"strings"
	"time"

	"github.com/adrg/frontmatter"
	"github.com/mediocregopher/blog.mediocregopher.com/srv/cfg"
	cfgpkg "github.com/mediocregopher/blog.mediocregopher.com/srv/cfg"
	"github.com/mediocregopher/blog.mediocregopher.com/srv/post"
	"github.com/mediocregopher/mediocre-go-lib/v2/mctx"
	"github.com/mediocregopher/mediocre-go-lib/v2/mlog"
)

type postFrontmatter struct {
	Title       string `yaml:"title"`
	Description string `yaml:"description"`
	Tags        string `yaml:"tags"`
	Series      string `yaml:"series"`
	Updated     string `yaml:"updated"`
}

func parseDate(str string) (time.Time, error) {
	const layout = "2006-01-02"
	return time.Parse(layout, str)
}

var postNameRegexp = regexp.MustCompile(`(20..-..-..)-([^.]+).md`)

func importPost(postStore post.Store, path string) (post.StoredPost, error) {

	fileName := filepath.Base(path)
	fileNameMatches := postNameRegexp.FindStringSubmatch(fileName)

	if len(fileNameMatches) != 3 {
		return post.StoredPost{}, fmt.Errorf("file name %q didn't match regex", fileName)
	}

	publishedAtStr := fileNameMatches[1]
	publishedAt, err := parseDate(publishedAtStr)
	if err != nil {
		return post.StoredPost{}, fmt.Errorf("parsing publish date %q: %w", publishedAtStr, err)
	}

	postID := fileNameMatches[2]

	f, err := os.Open(path)
	if err != nil {
		return post.StoredPost{}, fmt.Errorf("opening file: %w", err)
	}
	defer f.Close()

	var matter postFrontmatter

	body, err := frontmatter.Parse(f, &matter)

	if err != nil {
		return post.StoredPost{}, fmt.Errorf("parsing frontmatter: %w", err)
	}

	// if there is already a post for this ID, delete it, we're overwriting
	if err := postStore.Delete(postID); err != nil {
		return post.StoredPost{}, fmt.Errorf("deleting post id %q: %w", postID, err)
	}

	p := post.Post{
		ID:          postID,
		Title:       matter.Title,
		Description: matter.Description,
		Tags:        strings.Fields(matter.Tags),
		Series:      matter.Series,
		Body:        string(body),
	}

	if err := postStore.Set(p, publishedAt); err != nil {
		return post.StoredPost{}, fmt.Errorf("storing post id %q: %w", p.ID, err)
	}

	if matter.Updated != "" {

		lastUpdatedAt, err := parseDate(matter.Updated)
		if err != nil {
			return post.StoredPost{}, fmt.Errorf("parsing updated date %q: %w", matter.Updated, err)
		}

		// as a hack, we store the post again with the updated date as now. This
		// will update the LastUpdatedAt field in the Store.
		if err := postStore.Set(p, lastUpdatedAt); err != nil {
			return post.StoredPost{}, fmt.Errorf("updating post id %q: %w", p.ID, err)
		}
	}

	storedPost, err := postStore.GetByID(p.ID)
	if err != nil {
		return post.StoredPost{}, fmt.Errorf("retrieving stored post by id %q: %w", p.ID, err)
	}

	return storedPost, nil
}

func main() {

	ctx := context.Background()

	cfg := cfg.NewBlogCfg(cfg.Params{})

	var dataDir cfgpkg.DataDir
	dataDir.SetupCfg(cfg)
	defer dataDir.Close()
	ctx = mctx.WithAnnotator(ctx, &dataDir)

	paths := cfg.Args("<post file paths...>")

	// initialization
	err := cfg.Init(ctx)

	logger := mlog.NewLogger(nil)
	defer logger.Close()

	logger.Info(ctx, "process started")
	defer logger.Info(ctx, "process exiting")

	if err != nil {
		logger.Fatal(ctx, "initializing", err)
	}

	if len(*paths) == 0 {
		logger.FatalString(ctx, "no paths given")
	}

	postDB, err := post.NewSQLDB(dataDir)
	if err != nil {
		logger.Fatal(ctx, "initializing post sql db", err)
	}
	defer postDB.Close()

	postStore := post.NewStore(postDB)

	for _, path := range *paths {

		ctx := mctx.Annotate(ctx, "postPath", path)

		storedPost, err := importPost(postStore, path)
		if err != nil {
			logger.Error(ctx, "importing post", err)
		}

		ctx = mctx.Annotate(ctx,
			"postID", storedPost.ID,
			"postTitle", storedPost.Title,
			"postDescription", storedPost.Description,
			"postTags", storedPost.Tags,
			"postSeries", storedPost.Series,
			"postPublishedAt", storedPost.PublishedAt,
		)

		if !storedPost.LastUpdatedAt.IsZero() {
			ctx = mctx.Annotate(ctx,
				"postLastUpdatedAt", storedPost.LastUpdatedAt)
		}

		logger.Info(ctx, "post stored")
	}
}