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}