+9
-9
appview/db/artifact.go
+9
-9
appview/db/artifact.go
···
23
BlobCid cid.Cid
24
Name string
25
Size uint64
26
-
Mimetype string
27
}
28
29
func (a *Artifact) ArtifactAt() syntax.ATURI {
···
52
artifact.BlobCid.String(),
53
artifact.Name,
54
artifact.Size,
55
-
artifact.Mimetype,
56
)
57
return err
58
}
59
60
-
type Filter struct {
61
key string
62
arg any
63
}
64
65
-
func NewFilter(key string, arg any) Filter {
66
-
return Filter{
67
key: key,
68
arg: arg,
69
}
70
}
71
72
-
func (f Filter) Condition() string {
73
return fmt.Sprintf("%s = ?", f.key)
74
}
75
76
-
func GetArtifact(e Execer, filters ...Filter) ([]Artifact, error) {
77
var artifacts []Artifact
78
79
var conditions []string
···
124
&blobCid,
125
&artifact.Name,
126
&artifact.Size,
127
-
&artifact.Mimetype,
128
); err != nil {
129
return nil, err
130
}
···
146
return artifacts, nil
147
}
148
149
-
func RemoveArtifact(e Execer, filters ...Filter) error {
150
var conditions []string
151
var args []any
152
for _, filter := range filters {
···
23
BlobCid cid.Cid
24
Name string
25
Size uint64
26
+
MimeType string
27
}
28
29
func (a *Artifact) ArtifactAt() syntax.ATURI {
···
52
artifact.BlobCid.String(),
53
artifact.Name,
54
artifact.Size,
55
+
artifact.MimeType,
56
)
57
return err
58
}
59
60
+
type filter struct {
61
key string
62
arg any
63
}
64
65
+
func Filter(key string, arg any) filter {
66
+
return filter{
67
key: key,
68
arg: arg,
69
}
70
}
71
72
+
func (f filter) Condition() string {
73
return fmt.Sprintf("%s = ?", f.key)
74
}
75
76
+
func GetArtifact(e Execer, filters ...filter) ([]Artifact, error) {
77
var artifacts []Artifact
78
79
var conditions []string
···
124
&blobCid,
125
&artifact.Name,
126
&artifact.Size,
127
+
&artifact.MimeType,
128
); err != nil {
129
return nil, err
130
}
···
146
return artifacts, nil
147
}
148
149
+
func DeleteArtifact(e Execer, filters ...filter) error {
150
var conditions []string
151
var args []any
152
for _, filter := range filters {
+56
-1
appview/ingester.go
+56
-1
appview/ingester.go
···
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 "tangled.sh/tangled.sh/core/api/tangled"
12
"tangled.sh/tangled.sh/core/appview/db"
13
)
14
···
36
ingestStar(&d, e)
37
case tangled.PublicKeyNSID:
38
ingestPublicKey(&d, e)
39
}
40
41
return err
···
131
132
return nil
133
}
···
5
"encoding/json"
6
"fmt"
7
"log"
8
+
"time"
9
10
"github.com/bluesky-social/indigo/atproto/syntax"
11
"github.com/bluesky-social/jetstream/pkg/models"
12
+
"github.com/go-git/go-git/v5/plumbing"
13
+
"github.com/ipfs/go-cid"
14
+
"tangled.sh/tangled.sh/core/api/tangled"
15
"tangled.sh/tangled.sh/core/appview/db"
16
)
17
···
39
ingestStar(&d, e)
40
case tangled.PublicKeyNSID:
41
ingestPublicKey(&d, e)
42
+
case tangled.RepoArtifactNSID:
43
+
ingestArtifact(&d, e)
44
}
45
46
return err
···
136
137
return nil
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
<a href="/{{ .RepoInfo.FullName }}/tags/{{ .Artifact.Tag.String }}/download/{{ .Artifact.Name | urlquery }}" class="no-underline hover:no-underline">
7
{{ .Artifact.Name }}
8
</a>
9
-
<span class="text-gray-500 dark:text-gray-400 pl-2">{{ byteFmt .Artifact.Size }}</span>
10
</div>
11
12
-
<div id="right-side" class="text-gray-500 dark:text-gray-400 flex items-center flex-shrink-0 gap-2">
13
<span title="{{ longTimeFmt .Artifact.CreatedAt }}" class="hidden md:inline">{{ timeFmt .Artifact.CreatedAt }}</span>
14
<span title="{{ longTimeFmt .Artifact.CreatedAt }}" class=" md:hidden">{{ shortTimeFmt .Artifact.CreatedAt }}</span>
15
16
<span class="select-none after:content-['·'] hidden md:inline"></span>
17
-
<span class="truncate max-w-[100px] hidden md:inline">{{ .Artifact.Mimetype }}</span>
18
19
-
{{ if and (.LoggedInUser) (eq .LoggedInUser.Did .Artifact.Did) }}
20
<button
21
id="delete-{{ $unique }}"
22
class="btn text-red-500 hover:text-red-700 dark:text-red-400 dark:hover:text-red-300 gap-2"
···
6
<a href="/{{ .RepoInfo.FullName }}/tags/{{ .Artifact.Tag.String }}/download/{{ .Artifact.Name | urlquery }}" class="no-underline hover:no-underline">
7
{{ .Artifact.Name }}
8
</a>
9
+
<span class="text-gray-500 dark:text-gray-400 pl-2 text-sm">{{ byteFmt .Artifact.Size }}</span>
10
</div>
11
12
+
<div id="right-side" class="text-gray-500 dark:text-gray-400 flex items-center flex-shrink-0 gap-2 text-sm">
13
<span title="{{ longTimeFmt .Artifact.CreatedAt }}" class="hidden md:inline">{{ timeFmt .Artifact.CreatedAt }}</span>
14
<span title="{{ longTimeFmt .Artifact.CreatedAt }}" class=" md:hidden">{{ shortTimeFmt .Artifact.CreatedAt }}</span>
15
16
<span class="select-none after:content-['·'] hidden md:inline"></span>
17
+
<span class="truncate max-w-[100px] hidden md:inline">{{ .Artifact.MimeType }}</span>
18
19
+
{{ if and .LoggedInUser (eq .LoggedInUser.Did .Artifact.Did) }}
20
<button
21
id="delete-{{ $unique }}"
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
func (s *State) AttachArtifact(w http.ResponseWriter, r *http.Request) {
24
user := s.auth.GetUser(r)
25
tagParam := chi.URLParam(r, "tag")
26
-
f, err := fullyResolvedRepo(r)
27
if err != nil {
28
log.Println("failed to get repo and knot", err)
29
s.pages.Notice(w, "upload", "failed to upload artifact, error in repo resolution")
···
98
BlobCid: cid.Cid(uploadBlobResp.Blob.Ref),
99
Name: handler.Filename,
100
Size: uint64(uploadBlobResp.Blob.Size),
101
-
Mimetype: uploadBlobResp.Blob.MimeType,
102
}
103
104
err = db.AddArtifact(tx, artifact)
···
126
func (s *State) DownloadArtifact(w http.ResponseWriter, r *http.Request) {
127
tagParam := chi.URLParam(r, "tag")
128
filename := chi.URLParam(r, "file")
129
-
f, err := fullyResolvedRepo(r)
130
if err != nil {
131
log.Println("failed to get repo and knot", err)
132
return
···
143
144
artifacts, err := db.GetArtifact(
145
s.db,
146
-
db.NewFilter("repo_at", f.RepoAt),
147
-
db.NewFilter("tag", tag.Tag.Hash[:]),
148
-
db.NewFilter("name", filename),
149
)
150
if err != nil {
151
log.Println("failed to get artifacts", err)
···
173
user := s.auth.GetUser(r)
174
tagParam := chi.URLParam(r, "tag")
175
filename := chi.URLParam(r, "file")
176
-
f, err := fullyResolvedRepo(r)
177
if err != nil {
178
log.Println("failed to get repo and knot", err)
179
return
···
185
186
artifacts, err := db.GetArtifact(
187
s.db,
188
-
db.NewFilter("repo_at", f.RepoAt),
189
-
db.NewFilter("tag", tag[:]),
190
-
db.NewFilter("name", filename),
191
)
192
if err != nil {
193
log.Println("failed to get artifacts", err)
···
226
}
227
defer tx.Rollback()
228
229
-
err = db.RemoveArtifact(tx,
230
-
db.NewFilter("repo_at", f.RepoAt),
231
-
db.NewFilter("tag", artifact.Tag[:]),
232
-
db.NewFilter("name", filename),
233
)
234
if err != nil {
235
log.Println("failed to remove artifact record from db", err)
···
23
func (s *State) AttachArtifact(w http.ResponseWriter, r *http.Request) {
24
user := s.auth.GetUser(r)
25
tagParam := chi.URLParam(r, "tag")
26
+
f, err := s.fullyResolvedRepo(r)
27
if err != nil {
28
log.Println("failed to get repo and knot", err)
29
s.pages.Notice(w, "upload", "failed to upload artifact, error in repo resolution")
···
98
BlobCid: cid.Cid(uploadBlobResp.Blob.Ref),
99
Name: handler.Filename,
100
Size: uint64(uploadBlobResp.Blob.Size),
101
+
MimeType: uploadBlobResp.Blob.MimeType,
102
}
103
104
err = db.AddArtifact(tx, artifact)
···
126
func (s *State) DownloadArtifact(w http.ResponseWriter, r *http.Request) {
127
tagParam := chi.URLParam(r, "tag")
128
filename := chi.URLParam(r, "file")
129
+
f, err := s.fullyResolvedRepo(r)
130
if err != nil {
131
log.Println("failed to get repo and knot", err)
132
return
···
143
144
artifacts, err := db.GetArtifact(
145
s.db,
146
+
db.Filter("repo_at", f.RepoAt),
147
+
db.Filter("tag", tag.Tag.Hash[:]),
148
+
db.Filter("name", filename),
149
)
150
if err != nil {
151
log.Println("failed to get artifacts", err)
···
173
user := s.auth.GetUser(r)
174
tagParam := chi.URLParam(r, "tag")
175
filename := chi.URLParam(r, "file")
176
+
f, err := s.fullyResolvedRepo(r)
177
if err != nil {
178
log.Println("failed to get repo and knot", err)
179
return
···
185
186
artifacts, err := db.GetArtifact(
187
s.db,
188
+
db.Filter("repo_at", f.RepoAt),
189
+
db.Filter("tag", tag[:]),
190
+
db.Filter("name", filename),
191
)
192
if err != nil {
193
log.Println("failed to get artifacts", err)
···
226
}
227
defer tx.Rollback()
228
229
+
err = db.DeleteArtifact(tx,
230
+
db.Filter("repo_at", f.RepoAt),
231
+
db.Filter("tag", artifact.Tag[:]),
232
+
db.Filter("name", filename),
233
)
234
if err != nil {
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
+1
-1
flake.nix
+1
-1
flake.nix