~bigbes/sr-ht-spec

ref: 2a7569f43a772345c1bc739089cbad2efae0e278 sr-ht-spec/web/reader.go -rw-r--r-- 3.8 KiB
2a7569f4 — Eugene Blikh feat: web — the Phase 2 read plane UI and its SCSS entry 27 days ago
                                                                                
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
package web

import (
	"context"
	"fmt"

	"sourcecraft.dev/bigbes/sr-ht-spec/core"
	"sourcecraft.dev/bigbes/sr-ht-spec/doc"
	"sourcecraft.dev/bigbes/sr-ht-spec/search"
	"sourcecraft.dev/bigbes/sr-ht-spec/service"
)

// Snapshot is one space at one *resolved* revision: the addressable document
// set plus every document's bytes.
//
// Rev is always a commit sha, never a branch name, even when the request
// carried no ?rev= at all. The read plane resolves first and renders second, so
// a merge landing mid-request cannot make one page describe two revisions —
// and the sha it resolved to is the value the page offers as its permalink.
//
// Bodies is keyed by tree path, the same key doc.Page.Path carries. It is
// present because doc.Page.Links is filled by a render pass rather than by
// Scan, so backlinks require rendering every document in the space; see
// linkPass.
type Snapshot struct {
	Ref     core.SpaceRef
	Rev     string
	Archive *doc.Archive
	Bodies  map[string][]byte
}

// Reader is the read surface this package needs. *service.Service provides it
// through NewReader.
//
// It is an interface for the same reason compare.sr.ht's authz.Authorizer is:
// so the handlers can be tested against a document set rather than against a
// Postgres instance and a tree of bare repositories.
type Reader interface {
	// ListSpaces returns every space on the instance. Single-user with no
	// visibility levels means there is nothing to filter — the list is the
	// whole corpus.
	ListSpaces(ctx context.Context) ([]core.SpaceRef, error)

	// Snapshot resolves rev (service.ApprovedRev for the approved head) and
	// returns the space at that revision.
	Snapshot(ctx context.Context, ref core.SpaceRef, rev string) (*Snapshot, error)

	// ReadDocument reads one document by tree path at a revision.
	ReadDocument(ctx context.Context, ref core.SpaceRef, rev, path string) (service.Document, error)
}

// Searcher is the keyword index. *search.Index satisfies it as declared.
type Searcher interface {
	Search(ctx context.Context, q search.Query) (search.Results, error)
}

// NewReader adapts a *service.Service to Reader.
func NewReader(svc *service.Service) Reader { return serviceReader{svc: svc} }

// serviceReader is the production Reader: everything goes through service/,
// which is the layer that is allowed to touch gitx and db.
type serviceReader struct{ svc *service.Service }

func (r serviceReader) ListSpaces(ctx context.Context) ([]core.SpaceRef, error) {
	spaces, err := r.svc.ListSpaces(ctx)
	if err != nil {
		return nil, err
	}
	refs := make([]core.SpaceRef, 0, len(spaces))
	for _, sp := range spaces {
		refs = append(refs, sp.Ref)
	}
	return refs, nil
}

func (r serviceReader) Snapshot(ctx context.Context, ref core.SpaceRef, rev string) (*Snapshot, error) {
	sp, err := r.svc.OpenSpace(ctx, ref)
	if err != nil {
		return nil, err
	}
	resolved, err := r.svc.ResolveRev(ctx, sp, rev)
	if err != nil {
		return nil, err
	}
	// Both reads below are issued against the resolved sha rather than against
	// the caller's rev, so the archive and the bodies are the same revision by
	// construction and cannot disagree if a merge lands between them.
	arc, err := doc.Scan(ctx, sp.Repo, ref, resolved)
	if err != nil {
		return nil, fmt.Errorf("web: scan %s at %s: %w", ref, resolved, err)
	}
	docs, err := r.svc.ListDocuments(ctx, sp, resolved)
	if err != nil {
		return nil, err
	}
	bodies := make(map[string][]byte, len(docs))
	for _, d := range docs {
		bodies[d.Path] = d.Data
	}
	return &Snapshot{Ref: ref, Rev: resolved, Archive: arc, Bodies: bodies}, nil
}

func (r serviceReader) ReadDocument(ctx context.Context, ref core.SpaceRef, rev, p string) (service.Document, error) {
	sp, err := r.svc.OpenSpace(ctx, ref)
	if err != nil {
		return service.Document{}, err
	}
	return r.svc.ReadDocument(ctx, sp, rev, p)
}