Parent directory

access.go

2127 bytes
 1package pages
 2
 3import (
 4	"context"
 5	"errors"
 6	"sort"
 7
 8	"github.com/charmbracelet/soft-serve/pkg/access"
 9	"github.com/charmbracelet/soft-serve/pkg/backend"
10	"github.com/charmbracelet/soft-serve/pkg/proto"
11)
12
13// ErrNotFound is returned when a repository must not be disclosed to a page visitor.
14var ErrNotFound = errors.New("page resource not found")
15
16// IsPublicReadable reports whether repo may be shown to an anonymous page visitor.
17func IsPublicReadable(repo proto.Repository, level access.AccessLevel) bool {
18	return repo != nil && !repo.IsPrivate() && level >= access.ReadOnlyAccess
19}
20
21// PublicRepository loads a repository only when it is publicly readable.
22func PublicRepository(ctx context.Context, name string) (proto.Repository, error) {
23	be := backend.FromContext(ctx)
24	if be == nil {
25		return nil, ErrNotFound
26	}
27
28	repo, err := be.Repository(ctx, name)
29	if err != nil || !IsPublicReadable(repo, be.AccessLevelForUser(ctx, name, nil)) {
30		return nil, ErrNotFound
31	}
32	return repo, nil
33}
34
35// HomepageRepositories returns visible public repositories in display order.
36func HomepageRepositories(ctx context.Context) ([]proto.Repository, error) {
37	be := backend.FromContext(ctx)
38	if be == nil {
39		return nil, ErrNotFound
40	}
41
42	repos, err := be.Repositories(ctx)
43	if err != nil {
44		return nil, err
45	}
46
47	return FilterHomepageRepositories(repos, func(repo proto.Repository) bool {
48		return IsPublicReadable(repo, be.AccessLevelForUser(ctx, repo.Name(), nil))
49	}), nil
50}
51
52// FilterHomepageRepositories applies homepage visibility rules and display order.
53func FilterHomepageRepositories(repos []proto.Repository, canRead func(proto.Repository) bool) []proto.Repository {
54	visible := make([]proto.Repository, 0, len(repos))
55	for _, repo := range repos {
56		if repo.Name() == ".soft-serve" || repo.IsHidden() || !canRead(repo) {
57			continue
58		}
59		visible = append(visible, repo)
60	}
61
62	sort.Slice(visible, func(i, j int) bool {
63		left, right := visible[i], visible[j]
64		if left.UpdatedAt().Equal(right.UpdatedAt()) {
65			return left.Name() < right.Name()
66		}
67		return left.UpdatedAt().After(right.UpdatedAt())
68	})
69	return visible
70}