···20 <div class="col-span-1 md:col-span-2">
21 <h2 class="text-sm pb-2 uppercase font-bold">Spindle</h2>
22 <p class="text-gray-500 dark:text-gray-400">
23- Choose a spindle to execute your workflows on. Spindles can be
24- selfhosted,
25 <a class="text-gray-500 dark:text-gray-400 underline" href="https://tangled.sh/@tangled.sh/core/blob/master/docs/spindle/hosting.md">
26 click to learn more.
27 </a>
28 </p>
29 </div>
30- <form hx-post="/{{ $.RepoInfo.FullName }}/settings/spindle" class="col-span-1 md:col-span-1 md:justify-self-end group flex gap-2 items-stretch">
31- <select
32- id="spindle"
33- name="spindle"
34- required
35- class="p-1 max-w-64 border border-gray-200 bg-white dark:bg-gray-800 dark:text-white dark:border-gray-700"
36- {{ if not $.RepoInfo.Roles.IsOwner }}disabled{{ end }}>
37- <option value="" disabled selected >
38- Choose a spindle
39- </option>
40- {{ range $.Spindles }}
41- <option value="{{ . }}" class="py-1" {{ if eq . $.CurrentSpindle }}selected{{ end }}>
42- {{ . }}
43 </option>
44- {{ end }}
45- </select>
46- <button class="btn flex gap-2 items-center" type="submit" {{ if not $.RepoInfo.Roles.IsOwner }}disabled{{ end }}>
47- {{ i "check" "size-4" }}
48- {{ i "loader-circle" "w-4 h-4 animate-spin hidden group-[.htmx-request]:inline" }}
49- </button>
50- </form>
0000051 </div>
52{{ end }}
53
···20 <div class="col-span-1 md:col-span-2">
21 <h2 class="text-sm pb-2 uppercase font-bold">Spindle</h2>
22 <p class="text-gray-500 dark:text-gray-400">
23+ Choose a spindle to execute your workflows on. Only repository owners
24+ can configure spindles. Spindles can be selfhosted,
25 <a class="text-gray-500 dark:text-gray-400 underline" href="https://tangled.sh/@tangled.sh/core/blob/master/docs/spindle/hosting.md">
26 click to learn more.
27 </a>
28 </p>
29 </div>
30+ {{ if not $.RepoInfo.Roles.IsOwner }}
31+ <div class="col-span-1 md:col-span-1 md:justify-self-end group flex gap-2 items-stretch">
32+ {{ or $.CurrentSpindle "No spindle configured" }}
33+ </div>
34+ {{ else }}
35+ <form hx-post="/{{ $.RepoInfo.FullName }}/settings/spindle" class="col-span-1 md:col-span-1 md:justify-self-end group flex gap-2 items-stretch">
36+ <select
37+ id="spindle"
38+ name="spindle"
39+ required
40+ class="p-1 max-w-64 border border-gray-200 bg-white dark:bg-gray-800 dark:text-white dark:border-gray-700">
41+ <option value="" disabled>
42+ Choose a spindle
43 </option>
44+ {{ range $.Spindles }}
45+ <option value="{{ . }}" class="py-1" {{ if eq . $.CurrentSpindle }}selected{{ end }}>
46+ {{ . }}
47+ </option>
48+ {{ end }}
49+ </select>
50+ <button class="btn flex gap-2 items-center" type="submit" {{ if not $.RepoInfo.Roles.IsOwner }}disabled{{ end }}>
51+ {{ i "check" "size-4" }}
52+ {{ i "loader-circle" "w-4 h-4 animate-spin hidden group-[.htmx-request]:inline" }}
53+ </button>
54+ </form>
55+ {{ end }}
56 </div>
57{{ end }}
58
+2-2
appview/repo/repo.go
···1225 f, err := rp.repoResolver.Resolve(r)
1226 user := rp.oauth.GetUser(r)
12271228- // all spindles that this user is a member of
1229- spindles, err := rp.enforcer.GetSpindlesForUser(user.Did)
1230 if err != nil {
1231 log.Println("failed to fetch spindles", err)
1232 return
···1225 f, err := rp.repoResolver.Resolve(r)
1226 user := rp.oauth.GetUser(r)
12271228+ // all spindles that the repo owner is a member of
1229+ spindles, err := rp.enforcer.GetSpindlesForUser(f.OwnerDid())
1230 if err != nil {
1231 log.Println("failed to fetch spindles", err)
1232 return
-1
knotserver/ingester.go
···316 defer func() {
317 eventTime := event.TimeUS
318 lastTimeUs := eventTime + 1
319- fmt.Println("lastTimeUs", lastTimeUs)
320 if err := h.db.SaveLastTimeUs(lastTimeUs); err != nil {
321 err = fmt.Errorf("(deferred) failed to save last time us: %w", err)
322 }
···316 defer func() {
317 eventTime := event.TimeUS
318 lastTimeUs := eventTime + 1
0319 if err := h.db.SaveLastTimeUs(lastTimeUs); err != nil {
320 err = fmt.Errorf("(deferred) failed to save last time us: %w", err)
321 }
+19-3
spindle/ingester.go
···3import (
4 "context"
5 "encoding/json"
06 "fmt"
7- "path/filepath"
89 "tangled.sh/tangled.sh/core/api/tangled"
10 "tangled.sh/tangled.sh/core/eventconsumer"
···100 return nil
101}
102103-func (s *Spindle) ingestRepo(_ context.Context, e *models.Event) error {
104 var err error
00105106 l := s.l.With("component", "ingester", "record", tangled.RepoNSID)
107···137 return fmt.Errorf("failed to add repo: %w", err)
138 }
13900000140 // add repo to rbac
141- if err := s.e.AddRepo(record.Owner, rbac.ThisServer, filepath.Join(record.Owner, record.Name)); err != nil {
142 l.Error("failed to add repo to enforcer", "error", err)
143 return fmt.Errorf("failed to add repo: %w", err)
000000000144 }
145146 // add this knot to the event consumer
···3import (
4 "context"
5 "encoding/json"
6+ "errors"
7 "fmt"
089 "tangled.sh/tangled.sh/core/api/tangled"
10 "tangled.sh/tangled.sh/core/eventconsumer"
···100 return nil
101}
102103+func (s *Spindle) ingestRepo(ctx context.Context, e *models.Event) error {
104 var err error
105+ did := e.Did
106+ resolver := idresolver.DefaultResolver()
107108 l := s.l.With("component", "ingester", "record", tangled.RepoNSID)
109···139 return fmt.Errorf("failed to add repo: %w", err)
140 }
141142+ didSlashRepo, err := securejoin.SecureJoin(record.Owner, record.Name)
143+ if err != nil {
144+ return err
145+ }
146+147 // add repo to rbac
148+ if err := s.e.AddRepo(record.Owner, rbac.ThisServer, didSlashRepo); err != nil {
149 l.Error("failed to add repo to enforcer", "error", err)
150 return fmt.Errorf("failed to add repo: %w", err)
151+ }
152+153+ // add collaborators to rbac
154+ owner, err := resolver.ResolveIdent(ctx, did)
155+ if err != nil || owner.Handle.IsInvalidHandle() {
156+ return err
157+ }
158+ if err := s.fetchAndAddCollaborators(ctx, owner, didSlashRepo); err != nil {
159+ return err
160 }
161162 // add this knot to the event consumer