+7
-2
appview/db/spindle.go
+7
-2
appview/db/spindle.go
···
51
for rows.Next() {
52
var spindle Spindle
53
var createdAt string
54
+
var verified sql.NullString
55
56
if err := rows.Scan(
57
&spindle.Id,
···
69
}
70
71
if verified.Valid {
72
+
t, err := time.Parse(time.RFC3339, verified.String)
73
+
if err != nil {
74
+
now := time.Now()
75
+
spindle.Verified = &now
76
+
}
77
+
spindle.Verified = &t
78
}
79
80
spindles = append(spindles, spindle)
+2
-2
appview/pages/templates/repo/new.html
+2
-2
appview/pages/templates/repo/new.html
···
60
</fieldset>
61
62
<div class="space-y-2">
63
-
<button type="submit" class="btn flex gap-2 items-center">
64
create repo
65
<span id="spinner" class="group">
66
-
{{ i "loader-circle" "w-4 h-4 animate-spin hidden group-[.htmx-request]:inline" }}
67
</span>
68
</button>
69
<div id="repo" class="error"></div>
···
60
</fieldset>
61
62
<div class="space-y-2">
63
+
<button type="submit" class="btn flex items-center">
64
create repo
65
<span id="spinner" class="group">
66
+
{{ i "loader-circle" "ml-2 w-4 h-4 animate-spin hidden group-[.htmx-request]:inline" }}
67
</span>
68
</button>
69
<div id="repo" class="error"></div>
+3
-3
appview/pages/templates/repo/settings.html
+3
-3
appview/pages/templates/repo/settings.html
···
83
84
{{ if .RepoInfo.Roles.IsOwner }}
85
<form
86
-
hx-put="/{{ $.RepoInfo.FullName }}/settings/spindle"
87
class="mt-6 group"
88
>
89
<label for="spindle">spindle</label>
···
100
<option
101
value="{{ . }}"
102
class="py-1"
103
-
{{ if .eq . $.Repo.Spindle }}
104
selected
105
{{ end }}
106
>
···
127
<button class="btn my-2 flex items-center" type="text">
128
<span>delete</span>
129
<span id="delete-repo-spinner" class="group">
130
-
{{ i "loader-circle" "pl-2 w-4 h-4 animate-spin hidden group-[.htmx-request]:inline" }}
131
</span>
132
</button>
133
<span>
···
83
84
{{ if .RepoInfo.Roles.IsOwner }}
85
<form
86
+
hx-post="/{{ $.RepoInfo.FullName }}/settings/spindle"
87
class="mt-6 group"
88
>
89
<label for="spindle">spindle</label>
···
100
<option
101
value="{{ . }}"
102
class="py-1"
103
+
{{ if eq . $.CurrentSpindle }}
104
selected
105
{{ end }}
106
>
···
127
<button class="btn my-2 flex items-center" type="text">
128
<span>delete</span>
129
<span id="delete-repo-spinner" class="group">
130
+
{{ i "loader-circle" "ml-2 w-4 h-4 animate-spin hidden group-[.htmx-request]:inline" }}
131
</span>
132
</button>
133
<span>
+2
-2
appview/pages/templates/spindles/fragments/spindleListing.html
+2
-2
appview/pages/templates/spindles/fragments/spindleListing.html
···
24
<button
25
class="btn text-red-500 hover:text-red-700 dark:text-red-400 dark:hover:text-red-300 gap-2 group"
26
title="Delete spindle"
27
-
hx-delete="/spindles/{{ urlquery .Instance }}"
28
hx-swap="outerHTML"
29
hx-target="#spindle-{{.Id}}"
30
hx-confirm="Are you sure you want to delete the spindle '{{ .Instance }}'?"
···
40
<button
41
class="btn gap-2 group"
42
title="Retry spindle verification"
43
-
hx-post="/spindles/{{ urlquery .Instance }}/retry"
44
hx-swap="none"
45
hx-target="#spindle-{{.Id}}"
46
>
···
24
<button
25
class="btn text-red-500 hover:text-red-700 dark:text-red-400 dark:hover:text-red-300 gap-2 group"
26
title="Delete spindle"
27
+
hx-delete="/spindles/{{ .Instance }}"
28
hx-swap="outerHTML"
29
hx-target="#spindle-{{.Id}}"
30
hx-confirm="Are you sure you want to delete the spindle '{{ .Instance }}'?"
···
40
<button
41
class="btn gap-2 group"
42
title="Retry spindle verification"
43
+
hx-post="/spindles/{{ .Instance }}/retry"
44
hx-swap="none"
45
hx-target="#spindle-{{.Id}}"
46
>
+1
appview/repo/repo.go
+1
appview/repo/repo.go
+46
appview/spindles/spindles.go
+46
appview/spindles/spindles.go
···
54
)
55
if err != nil {
56
s.Logger.Error("failed to fetch spindles", "err", err)
57
}
58
59
s.Pages.Spindles(w, pages.SpindlesParams{
···
150
return
151
}
152
153
// ok
154
s.Pages.HxRefresh(w)
155
return
···
255
db.FilterEq("owner", user.Did),
256
db.FilterEq("instance", instance),
257
)
258
259
verifiedSpindle := db.Spindle{
260
Id: int(rowId),
···
54
)
55
if err != nil {
56
s.Logger.Error("failed to fetch spindles", "err", err)
57
+
w.WriteHeader(http.StatusInternalServerError)
58
+
return
59
}
60
61
s.Pages.Spindles(w, pages.SpindlesParams{
···
152
return
153
}
154
155
+
tx, err = s.Db.Begin()
156
+
if err != nil {
157
+
l.Error("failed to commit verification info", "err", err)
158
+
s.Pages.HxRefresh(w)
159
+
return
160
+
}
161
+
defer func() {
162
+
tx.Rollback()
163
+
s.Enforcer.E.LoadPolicy()
164
+
}()
165
+
166
+
// mark this spindle as verified in the db
167
+
_, err = db.VerifySpindle(
168
+
tx,
169
+
db.FilterEq("owner", user.Did),
170
+
db.FilterEq("instance", instance),
171
+
)
172
+
173
+
err = s.Enforcer.AddSpindleOwner(instance, user.Did)
174
+
if err != nil {
175
+
l.Error("failed to update ACL", "err", err)
176
+
s.Pages.HxRefresh(w)
177
+
return
178
+
}
179
+
180
+
err = tx.Commit()
181
+
if err != nil {
182
+
l.Error("failed to commit verification info", "err", err)
183
+
s.Pages.HxRefresh(w)
184
+
return
185
+
}
186
+
187
+
err = s.Enforcer.E.SavePolicy()
188
+
if err != nil {
189
+
l.Error("failed to update ACL", "err", err)
190
+
s.Pages.HxRefresh(w)
191
+
return
192
+
}
193
+
194
// ok
195
s.Pages.HxRefresh(w)
196
return
···
296
db.FilterEq("owner", user.Did),
297
db.FilterEq("instance", instance),
298
)
299
+
if err != nil {
300
+
l.Error("verification failed", "err", err)
301
+
fail()
302
+
return
303
+
}
304
305
verifiedSpindle := db.Spindle{
306
Id: int(rowId),
+6
-5
spindle/ingester.go
+6
-5
spindle/ingester.go
···
59
if s.cfg.Server.Dev {
60
domain = s.cfg.Server.ListenAddr
61
}
62
-
recordInstance := *record.Instance
63
64
if recordInstance != domain {
65
l.Error("domain mismatch", "domain", recordInstance, "expected", domain)
66
-
return fmt.Errorf("domain mismatch: %s != %s", *record.Instance, domain)
67
}
68
69
ok, err := s.e.E.Enforce(did, rbacDomain, rbacDomain, "server:invite")
···
95
96
l := s.l.With("component", "ingester", "record", tangled.RepoNSID)
97
98
switch e.Commit.Operation {
99
case models.CommitOperationCreate, models.CommitOperationUpdate:
100
raw := e.Commit.Record
···
106
}
107
108
domain := s.cfg.Server.Hostname
109
-
if s.cfg.Server.Dev {
110
-
domain = s.cfg.Server.ListenAddr
111
-
}
112
113
// no spindle configured for this repo
114
if record.Spindle == nil {
115
return nil
116
}
117
118
// this repo did not want this spindle
119
if *record.Spindle != domain {
120
return nil
121
}
122
···
59
if s.cfg.Server.Dev {
60
domain = s.cfg.Server.ListenAddr
61
}
62
+
recordInstance := record.Instance
63
64
if recordInstance != domain {
65
l.Error("domain mismatch", "domain", recordInstance, "expected", domain)
66
+
return fmt.Errorf("domain mismatch: %s != %s", record.Instance, domain)
67
}
68
69
ok, err := s.e.E.Enforce(did, rbacDomain, rbacDomain, "server:invite")
···
95
96
l := s.l.With("component", "ingester", "record", tangled.RepoNSID)
97
98
+
l.Info("ingesting repo record")
99
+
100
switch e.Commit.Operation {
101
case models.CommitOperationCreate, models.CommitOperationUpdate:
102
raw := e.Commit.Record
···
108
}
109
110
domain := s.cfg.Server.Hostname
111
112
// no spindle configured for this repo
113
if record.Spindle == nil {
114
+
l.Info("no spindle configured", "did", record.Owner, "name", record.Name)
115
return nil
116
}
117
118
// this repo did not want this spindle
119
if *record.Spindle != domain {
120
+
l.Info("different spindle configured", "did", record.Owner, "name", record.Name, "spindle", *record.Spindle, "domain", domain)
121
return nil
122
}
123
+7
-1
spindle/server.go
+7
-1
spindle/server.go
···
66
67
jq := queue.NewQueue(100, 2)
68
69
-
collections := []string{tangled.SpindleMemberNSID}
70
jc, err := jetstream.NewJetstreamClient(cfg.Server.JetstreamEndpoint, "spindle", collections, nil, logger, d, false, false)
71
if err != nil {
72
return fmt.Errorf("failed to setup jetstream client: %w", err)
···
140
mux := chi.NewRouter()
141
142
mux.HandleFunc("/events", s.Events)
143
mux.HandleFunc("/logs/{knot}/{rkey}/{name}", s.Logs)
144
return mux
145
}
···
66
67
jq := queue.NewQueue(100, 2)
68
69
+
collections := []string{
70
+
tangled.SpindleMemberNSID,
71
+
tangled.RepoNSID,
72
+
}
73
jc, err := jetstream.NewJetstreamClient(cfg.Server.JetstreamEndpoint, "spindle", collections, nil, logger, d, false, false)
74
if err != nil {
75
return fmt.Errorf("failed to setup jetstream client: %w", err)
···
143
mux := chi.NewRouter()
144
145
mux.HandleFunc("/events", s.Events)
146
+
mux.HandleFunc("/owner", func(w http.ResponseWriter, r *http.Request) {
147
+
w.Write([]byte(s.cfg.Server.Owner))
148
+
})
149
mux.HandleFunc("/logs/{knot}/{rkey}/{name}", s.Logs)
150
return mux
151
}