+9
-9
appview/db/artifact.go
+9
-9
appview/db/artifact.go
···
23
23
BlobCid cid.Cid
24
24
Name string
25
25
Size uint64
26
-
Mimetype string
26
+
MimeType string
27
27
}
28
28
29
29
func (a *Artifact) ArtifactAt() syntax.ATURI {
···
52
52
artifact.BlobCid.String(),
53
53
artifact.Name,
54
54
artifact.Size,
55
-
artifact.Mimetype,
55
+
artifact.MimeType,
56
56
)
57
57
return err
58
58
}
59
59
60
-
type Filter struct {
60
+
type filter struct {
61
61
key string
62
62
arg any
63
63
}
64
64
65
-
func NewFilter(key string, arg any) Filter {
66
-
return Filter{
65
+
func Filter(key string, arg any) filter {
66
+
return filter{
67
67
key: key,
68
68
arg: arg,
69
69
}
70
70
}
71
71
72
-
func (f Filter) Condition() string {
72
+
func (f filter) Condition() string {
73
73
return fmt.Sprintf("%s = ?", f.key)
74
74
}
75
75
76
-
func GetArtifact(e Execer, filters ...Filter) ([]Artifact, error) {
76
+
func GetArtifact(e Execer, filters ...filter) ([]Artifact, error) {
77
77
var artifacts []Artifact
78
78
79
79
var conditions []string
···
124
124
&blobCid,
125
125
&artifact.Name,
126
126
&artifact.Size,
127
-
&artifact.Mimetype,
127
+
&artifact.MimeType,
128
128
); err != nil {
129
129
return nil, err
130
130
}
···
146
146
return artifacts, nil
147
147
}
148
148
149
-
func RemoveArtifact(e Execer, filters ...Filter) error {
149
+
func DeleteArtifact(e Execer, filters ...filter) error {
150
150
var conditions []string
151
151
var args []any
152
152
for _, filter := range filters {
+56
-1
appview/ingester.go
+56
-1
appview/ingester.go
···
5
5
"encoding/json"
6
6
"fmt"
7
7
"log"
8
+
"time"
8
9
9
10
"github.com/bluesky-social/indigo/atproto/syntax"
10
11
"github.com/bluesky-social/jetstream/pkg/models"
11
-
tangled "tangled.sh/tangled.sh/core/api/tangled"
12
+
"github.com/go-git/go-git/v5/plumbing"
13
+
"github.com/ipfs/go-cid"
14
+
"tangled.sh/tangled.sh/core/api/tangled"
12
15
"tangled.sh/tangled.sh/core/appview/db"
13
16
)
14
17
···
36
39
ingestStar(&d, e)
37
40
case tangled.PublicKeyNSID:
38
41
ingestPublicKey(&d, e)
42
+
case tangled.RepoArtifactNSID:
43
+
ingestArtifact(&d, e)
39
44
}
40
45
41
46
return err
···
131
136
132
137
return nil
133
138
}
139
+
140
+
func ingestArtifact(d *db.DbWrapper, e *models.Event) error {
141
+
did := e.Did
142
+
var err error
143
+
144
+
switch e.Commit.Operation {
145
+
case models.CommitOperationCreate, models.CommitOperationUpdate:
146
+
log.Println("processing add of artifact")
147
+
raw := json.RawMessage(e.Commit.Record)
148
+
record := tangled.RepoArtifact{}
149
+
err = json.Unmarshal(raw, &record)
150
+
if err != nil {
151
+
log.Printf("invalid record: %s", err)
152
+
return err
153
+
}
154
+
155
+
repoAt, err := syntax.ParseATURI(record.Repo)
156
+
if err != nil {
157
+
return err
158
+
}
159
+
160
+
createdAt, err := time.Parse(time.RFC3339, record.CreatedAt)
161
+
if err != nil {
162
+
createdAt = time.Now()
163
+
}
164
+
165
+
artifact := db.Artifact{
166
+
Did: did,
167
+
Rkey: e.Commit.RKey,
168
+
RepoAt: repoAt,
169
+
Tag: plumbing.Hash(record.Tag),
170
+
CreatedAt: createdAt,
171
+
BlobCid: cid.Cid(record.Artifact.Ref),
172
+
Name: record.Name,
173
+
Size: uint64(record.Artifact.Size),
174
+
MimeType: record.Artifact.MimeType,
175
+
}
176
+
177
+
err = db.AddArtifact(d, artifact)
178
+
case models.CommitOperationDelete:
179
+
log.Println("processing delete of artifact")
180
+
err = db.DeleteArtifact(d, db.Filter("did", did), db.Filter("rkey", e.Commit.RKey))
181
+
}
182
+
183
+
if err != nil {
184
+
return fmt.Errorf("failed to %s artifact record: %w", e.Commit.Operation, err)
185
+
}
186
+
187
+
return nil
188
+
}
+1
-1
appview/pages/pages.go
+1
-1
appview/pages/pages.go
+4
-4
appview/pages/templates/repo/fragments/artifact.html
+4
-4
appview/pages/templates/repo/fragments/artifact.html
···
6
6
<a href="/{{ .RepoInfo.FullName }}/tags/{{ .Artifact.Tag.String }}/download/{{ .Artifact.Name | urlquery }}" class="no-underline hover:no-underline">
7
7
{{ .Artifact.Name }}
8
8
</a>
9
-
<span class="text-gray-500 dark:text-gray-400 pl-2">{{ byteFmt .Artifact.Size }}</span>
9
+
<span class="text-gray-500 dark:text-gray-400 pl-2 text-sm">{{ byteFmt .Artifact.Size }}</span>
10
10
</div>
11
11
12
-
<div id="right-side" class="text-gray-500 dark:text-gray-400 flex items-center flex-shrink-0 gap-2">
12
+
<div id="right-side" class="text-gray-500 dark:text-gray-400 flex items-center flex-shrink-0 gap-2 text-sm">
13
13
<span title="{{ longTimeFmt .Artifact.CreatedAt }}" class="hidden md:inline">{{ timeFmt .Artifact.CreatedAt }}</span>
14
14
<span title="{{ longTimeFmt .Artifact.CreatedAt }}" class=" md:hidden">{{ shortTimeFmt .Artifact.CreatedAt }}</span>
15
15
16
16
<span class="select-none after:content-['·'] hidden md:inline"></span>
17
-
<span class="truncate max-w-[100px] hidden md:inline">{{ .Artifact.Mimetype }}</span>
17
+
<span class="truncate max-w-[100px] hidden md:inline">{{ .Artifact.MimeType }}</span>
18
18
19
-
{{ if and (.LoggedInUser) (eq .LoggedInUser.Did .Artifact.Did) }}
19
+
{{ if and .LoggedInUser (eq .LoggedInUser.Did .Artifact.Did) }}
20
20
<button
21
21
id="delete-{{ $unique }}"
22
22
class="btn text-red-500 hover:text-red-700 dark:text-red-400 dark:hover:text-red-300 gap-2"
+14
-14
appview/state/artifact.go
+14
-14
appview/state/artifact.go
···
23
23
func (s *State) AttachArtifact(w http.ResponseWriter, r *http.Request) {
24
24
user := s.auth.GetUser(r)
25
25
tagParam := chi.URLParam(r, "tag")
26
-
f, err := fullyResolvedRepo(r)
26
+
f, err := s.fullyResolvedRepo(r)
27
27
if err != nil {
28
28
log.Println("failed to get repo and knot", err)
29
29
s.pages.Notice(w, "upload", "failed to upload artifact, error in repo resolution")
···
98
98
BlobCid: cid.Cid(uploadBlobResp.Blob.Ref),
99
99
Name: handler.Filename,
100
100
Size: uint64(uploadBlobResp.Blob.Size),
101
-
Mimetype: uploadBlobResp.Blob.MimeType,
101
+
MimeType: uploadBlobResp.Blob.MimeType,
102
102
}
103
103
104
104
err = db.AddArtifact(tx, artifact)
···
126
126
func (s *State) DownloadArtifact(w http.ResponseWriter, r *http.Request) {
127
127
tagParam := chi.URLParam(r, "tag")
128
128
filename := chi.URLParam(r, "file")
129
-
f, err := fullyResolvedRepo(r)
129
+
f, err := s.fullyResolvedRepo(r)
130
130
if err != nil {
131
131
log.Println("failed to get repo and knot", err)
132
132
return
···
143
143
144
144
artifacts, err := db.GetArtifact(
145
145
s.db,
146
-
db.NewFilter("repo_at", f.RepoAt),
147
-
db.NewFilter("tag", tag.Tag.Hash[:]),
148
-
db.NewFilter("name", filename),
146
+
db.Filter("repo_at", f.RepoAt),
147
+
db.Filter("tag", tag.Tag.Hash[:]),
148
+
db.Filter("name", filename),
149
149
)
150
150
if err != nil {
151
151
log.Println("failed to get artifacts", err)
···
173
173
user := s.auth.GetUser(r)
174
174
tagParam := chi.URLParam(r, "tag")
175
175
filename := chi.URLParam(r, "file")
176
-
f, err := fullyResolvedRepo(r)
176
+
f, err := s.fullyResolvedRepo(r)
177
177
if err != nil {
178
178
log.Println("failed to get repo and knot", err)
179
179
return
···
185
185
186
186
artifacts, err := db.GetArtifact(
187
187
s.db,
188
-
db.NewFilter("repo_at", f.RepoAt),
189
-
db.NewFilter("tag", tag[:]),
190
-
db.NewFilter("name", filename),
188
+
db.Filter("repo_at", f.RepoAt),
189
+
db.Filter("tag", tag[:]),
190
+
db.Filter("name", filename),
191
191
)
192
192
if err != nil {
193
193
log.Println("failed to get artifacts", err)
···
226
226
}
227
227
defer tx.Rollback()
228
228
229
-
err = db.RemoveArtifact(tx,
230
-
db.NewFilter("repo_at", f.RepoAt),
231
-
db.NewFilter("tag", artifact.Tag[:]),
232
-
db.NewFilter("name", filename),
229
+
err = db.DeleteArtifact(tx,
230
+
db.Filter("repo_at", f.RepoAt),
231
+
db.Filter("tag", artifact.Tag[:]),
232
+
db.Filter("name", filename),
233
233
)
234
234
if err != nil {
235
235
log.Println("failed to remove artifact record from db", err)
-70
appview/state/jetstream.go
-70
appview/state/jetstream.go
···
1
-
package state
2
-
3
-
import (
4
-
"context"
5
-
"encoding/json"
6
-
"fmt"
7
-
"log"
8
-
9
-
"github.com/bluesky-social/indigo/atproto/syntax"
10
-
"github.com/bluesky-social/jetstream/pkg/models"
11
-
"tangled.sh/tangled.sh/core/api/tangled"
12
-
"tangled.sh/tangled.sh/core/appview/db"
13
-
)
14
-
15
-
type Ingester func(ctx context.Context, e *models.Event) error
16
-
17
-
func jetstreamIngester(d db.DbWrapper) Ingester {
18
-
return func(ctx context.Context, e *models.Event) error {
19
-
var err error
20
-
defer func() {
21
-
eventTime := e.TimeUS
22
-
lastTimeUs := eventTime + 1
23
-
if err := d.SaveLastTimeUs(lastTimeUs); err != nil {
24
-
err = fmt.Errorf("(deferred) failed to save last time us: %w", err)
25
-
}
26
-
}()
27
-
28
-
if e.Kind != models.EventKindCommit {
29
-
return nil
30
-
}
31
-
32
-
did := e.Did
33
-
raw := json.RawMessage(e.Commit.Record)
34
-
35
-
switch e.Commit.Collection {
36
-
case tangled.GraphFollowNSID:
37
-
record := tangled.GraphFollow{}
38
-
err := json.Unmarshal(raw, &record)
39
-
if err != nil {
40
-
log.Println("invalid record")
41
-
return err
42
-
}
43
-
err = db.AddFollow(d, did, record.Subject, e.Commit.RKey)
44
-
if err != nil {
45
-
return fmt.Errorf("failed to add follow to db: %w", err)
46
-
}
47
-
case tangled.FeedStarNSID:
48
-
record := tangled.FeedStar{}
49
-
err := json.Unmarshal(raw, &record)
50
-
if err != nil {
51
-
log.Println("invalid record")
52
-
return err
53
-
}
54
-
55
-
subjectUri, err := syntax.ParseATURI(record.Subject)
56
-
57
-
if err != nil {
58
-
log.Println("invalid record")
59
-
return err
60
-
}
61
-
62
-
err = db.AddStar(d, did, subjectUri, e.Commit.RKey)
63
-
if err != nil {
64
-
return fmt.Errorf("failed to add follow to db: %w", err)
65
-
}
66
-
}
67
-
68
-
return err
69
-
}
70
-
}
+1
-1
appview/state/repo.go
+1
-1
appview/state/repo.go
+1
-1
appview/state/state.go
+1
-1
appview/state/state.go
···
63
63
jc, err := jetstream.NewJetstreamClient(
64
64
config.JetstreamEndpoint,
65
65
"appview",
66
-
[]string{tangled.GraphFollowNSID, tangled.FeedStarNSID, tangled.PublicKeyNSID},
66
+
[]string{tangled.GraphFollowNSID, tangled.FeedStarNSID, tangled.PublicKeyNSID, tangled.RepoArtifactNSID},
67
67
nil,
68
68
slog.Default(),
69
69
wrapper,
+1
-1
flake.nix
+1
-1
flake.nix
···
49
49
inherit (gitignore.lib) gitignoreSource;
50
50
in {
51
51
overlays.default = final: prev: let
52
-
goModHash = "sha256-EilWxfqrcKDaSR5zA3ZuDSCq7V+/IfWpKPu8HWhpndA=";
52
+
goModHash = "sha256-CmBuvv3duQQoc8iTW4244w1rYLGeqMQS+qQ3wwReZZg=";
53
53
buildCmdPackage = name:
54
54
final.buildGoModule {
55
55
pname = name;