Artifact [19e7ea52d3]
Not logged in

Artifact 19e7ea52d331aa1b9be296172311c47f9c433406c081f3988a701d7f4e19a4a4:


package api

import (
	"context"
	"sort"

	"craftdesign.group/quarry/internal/index"
	"craftdesign.group/quarry/internal/ops"
)

// queuedLanding is one open landing the caller could accept, with the
// repository it is in: the queue spans every repository, so a row that
// did not say where it was would not be a link anybody could follow.
type queuedLanding struct {
	Repo string `json:"repo"`
	index.LandingSummary
}

type landingQueueIn struct{}

type landingQueueOut struct {
	Landings []queuedLanding `json:"landings"`
	Count    int             `json:"count"`
}

// registerLandingQueue wires `landing.queue`, the reviewer's side of
// landings. Who opened a landing is the least interesting fact about it
// to the person who has to decide it; this lists the decisions.
func registerLandingQueue(r *ops.Registry, d Deps) error {
	return ops.Register(r, ops.Op[landingQueueIn, landingQueueOut]{
		Name: "landing.queue",
		Doc: "Every open landing, across all repositories, that the signed-in account may accept: " +
			"it holds the check-in capability on the repository, and the target is not a protected " +
			"branch it does not own. Mirrors that refuse local writes are left out, as are landings " +
			"superseded by a newer one on the same branch. Newest activity first.",
		Kind:  ops.Query,
		Auth:  ops.Token,
		Route: "GET /api/v1/me/landings",
		Run: func(ctx context.Context, _ landingQueueIn) (landingQueueOut, error) {
			who := ops.User(ctx)
			if who == "" {
				return landingQueueOut{Landings: []queuedLanding{}}, nil
			}
			ls := d.landingQueue(ctx, who)
			return landingQueueOut{Landings: ls, Count: len(ls)}, nil
		},
	})
}

// landingQueue reads the open landings the account may accept. Each
// repository is asked the same two questions the accept itself asks
// (landing.accept: the "i" letter, then RFC 0007's MayJoin), so the
// queue never lists a landing whose button would refuse.
//
// A repository that cannot be read is skipped rather than failing the
// list: one broken index must not hide every other decision.
func (d Deps) landingQueue(ctx context.Context, who string) []queuedLanding {
	out := []queuedLanding{}
	for _, name := range d.Repos.Names() {
		if d.companionParent(name) != "" {
			continue
		}
		if ok, _ := d.Writable(name); !ok {
			continue
		}
		if d.CanRead != nil && !d.CanRead(ctx, name) {
			continue
		}
		if !d.mayAcceptIn(ctx, who, name) {
			continue
		}
		repo, ok := d.Repos.Get(name)
		if !ok || repo.Index == nil {
			continue
		}
		open, err := repo.Index.Landings("open", 0)
		if err != nil || len(open) == 0 {
			continue
		}
		branches := make([]string, 0, len(open))
		for _, l := range open {
			branches = append(branches, l.Branch)
		}
		verdicts, _ := repo.Index.VerdictsForLandings(branches)
		for _, l := range open {
			if l.SupersededBy != "" {
				continue
			}
			if d.Protect != nil && !d.Protect.MayJoin(name, who, l.Target) {
				continue
			}
			l.CI = verdicts[l.Branch]
			out = append(out, queuedLanding{Repo: name, LandingSummary: l})
		}
	}
	sort.SliceStable(out, func(i, j int) bool { return out[i].Updated > out[j].Updated })
	return out
}

// mayAcceptIn is the accept's capability check for a repository other
// than the one the request named. With a capability plane it asks the
// plane; without one it reads the request's own letters, and a forge
// with neither gates nothing, as every other op on it does.
func (d Deps) mayAcceptIn(ctx context.Context, who, repo string) bool {
	if d.CapsFor != nil {
		return d.CapsFor(who, repo).HasAny("i")
	}
	if ops.HasCaps(ctx) {
		return ops.CapsOf(ctx).HasAny("i")
	}
	return true
}