code.pablomurad.com

patchwork

Publishes the revisions of your pages as a feed, with notes and readable diffs, written in Go

internal/store/snapshots.go raw
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 store

import (
	"context"
	"database/sql"
	"errors"
	"time"
)

// Snapshot is one observed state of a page's file.
type Snapshot struct {
	ID              int64
	PageID          int64
	Seq             int
	ObservedAt      time.Time
	SourceMtime     *time.Time
	SourceRef       string // git commit in git mode
	RawHash         string
	ContentHash     string
	Raw             string // normalized file text
	FrontmatterJSON string
	Title           string
	Note            string
	Kind            string
	Maturity        string
}

const snapshotCols = `id, page_id, seq, observed_at, source_mtime, source_ref, raw_hash, content_hash, raw, frontmatter_json, title, note, kind, maturity`

func scanSnapshot(row interface{ Scan(...any) error }) (*Snapshot, error) {
	var s Snapshot
	var observed string
	var mtime sql.NullString
	if err := row.Scan(&s.ID, &s.PageID, &s.Seq, &observed, &mtime, &s.SourceRef, &s.RawHash, &s.ContentHash,
		&s.Raw, &s.FrontmatterJSON, &s.Title, &s.Note, &s.Kind, &s.Maturity); err != nil {
		if errors.Is(err, sql.ErrNoRows) {
			return nil, ErrNotFound
		}
		return nil, err
	}
	var err error
	if s.ObservedAt, err = parseTime(observed); err != nil {
		return nil, err
	}
	if s.SourceMtime, err = parseTimePtr(mtime); err != nil {
		return nil, err
	}
	return &s, nil
}

// InsertSnapshot stores a snapshot as the page's next one and sets ID and Seq.
func (q *Queries) InsertSnapshot(ctx context.Context, s *Snapshot) error {
	if err := q.q.QueryRowContext(ctx, `SELECT COALESCE(MAX(seq), 0) + 1 FROM snapshots WHERE page_id = ?`, s.PageID).Scan(&s.Seq); err != nil {
		return err
	}
	res, err := q.q.ExecContext(ctx,
		`INSERT INTO snapshots (page_id, seq, observed_at, source_mtime, source_ref, raw_hash, content_hash, raw, frontmatter_json, title, note, kind, maturity)
		 VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
		s.PageID, s.Seq, fmtTime(s.ObservedAt), fmtTimePtr(s.SourceMtime), s.SourceRef, s.RawHash, s.ContentHash,
		s.Raw, s.FrontmatterJSON, s.Title, s.Note, s.Kind, s.Maturity)
	if err != nil {
		return err
	}
	s.ID, err = res.LastInsertId()
	return err
}

// Snapshot returns a snapshot by id.
func (q *Queries) Snapshot(ctx context.Context, id int64) (*Snapshot, error) {
	return scanSnapshot(q.q.QueryRowContext(ctx, `SELECT `+snapshotCols+` FROM snapshots WHERE id = ?`, id))
}

// LastSnapshot returns the page's most recent snapshot.
func (q *Queries) LastSnapshot(ctx context.Context, pageID int64) (*Snapshot, error) {
	return scanSnapshot(q.q.QueryRowContext(ctx,
		`SELECT `+snapshotCols+` FROM snapshots WHERE page_id = ? ORDER BY seq DESC LIMIT 1`, pageID))
}

// SnapshotCount counts a page's snapshots, silent ones included.
func (q *Queries) SnapshotCount(ctx context.Context, pageID int64) (int, error) {
	var n int
	err := q.q.QueryRowContext(ctx, `SELECT COUNT(*) FROM snapshots WHERE page_id = ?`, pageID).Scan(&n)
	return n, err
}