package web
import (
"context"
"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 comes
// off the same tree walk as the archive, so a document listed here and a
// document in the archive are the same bytes at the same revision.
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
}
// One call, one tree walk, and the link graph filled in. This package used
// to scan the git tree itself and then list the documents a second time for
// their bodies — two walks per page view, and a reach past service/ into
// gitx that the layering rule forbids.
arc, bodies, err := r.svc.Archive(ctx, sp, rev)
if err != nil {
return nil, err
}
return &Snapshot{Ref: ref, Rev: arc.Rev, 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)
}