+164
api/tangled/cbor_gen.go
+164
api/tangled/cbor_gen.go
···
438
439
return nil
440
}
441
+
func (t *GraphFollow) MarshalCBOR(w io.Writer) error {
442
+
if t == nil {
443
+
_, err := w.Write(cbg.CborNull)
444
+
return err
445
+
}
446
+
447
+
cw := cbg.NewCborWriter(w)
448
+
449
+
if _, err := cw.Write([]byte{163}); err != nil {
450
+
return err
451
+
}
452
+
453
+
// t.LexiconTypeID (string) (string)
454
+
if len("$type") > 1000000 {
455
+
return xerrors.Errorf("Value in field \"$type\" was too long")
456
+
}
457
+
458
+
if err := cw.WriteMajorTypeHeader(cbg.MajTextString, uint64(len("$type"))); err != nil {
459
+
return err
460
+
}
461
+
if _, err := cw.WriteString(string("$type")); err != nil {
462
+
return err
463
+
}
464
+
465
+
if err := cw.WriteMajorTypeHeader(cbg.MajTextString, uint64(len("sh.tangled.graph.follow"))); err != nil {
466
+
return err
467
+
}
468
+
if _, err := cw.WriteString(string("sh.tangled.graph.follow")); err != nil {
469
+
return err
470
+
}
471
+
472
+
// t.Subject (string) (string)
473
+
if len("subject") > 1000000 {
474
+
return xerrors.Errorf("Value in field \"subject\" was too long")
475
+
}
476
+
477
+
if err := cw.WriteMajorTypeHeader(cbg.MajTextString, uint64(len("subject"))); err != nil {
478
+
return err
479
+
}
480
+
if _, err := cw.WriteString(string("subject")); err != nil {
481
+
return err
482
+
}
483
+
484
+
if len(t.Subject) > 1000000 {
485
+
return xerrors.Errorf("Value in field t.Subject was too long")
486
+
}
487
+
488
+
if err := cw.WriteMajorTypeHeader(cbg.MajTextString, uint64(len(t.Subject))); err != nil {
489
+
return err
490
+
}
491
+
if _, err := cw.WriteString(string(t.Subject)); err != nil {
492
+
return err
493
+
}
494
+
495
+
// t.CreatedAt (string) (string)
496
+
if len("createdAt") > 1000000 {
497
+
return xerrors.Errorf("Value in field \"createdAt\" was too long")
498
+
}
499
+
500
+
if err := cw.WriteMajorTypeHeader(cbg.MajTextString, uint64(len("createdAt"))); err != nil {
501
+
return err
502
+
}
503
+
if _, err := cw.WriteString(string("createdAt")); err != nil {
504
+
return err
505
+
}
506
+
507
+
if len(t.CreatedAt) > 1000000 {
508
+
return xerrors.Errorf("Value in field t.CreatedAt was too long")
509
+
}
510
+
511
+
if err := cw.WriteMajorTypeHeader(cbg.MajTextString, uint64(len(t.CreatedAt))); err != nil {
512
+
return err
513
+
}
514
+
if _, err := cw.WriteString(string(t.CreatedAt)); err != nil {
515
+
return err
516
+
}
517
+
return nil
518
+
}
519
+
520
+
func (t *GraphFollow) UnmarshalCBOR(r io.Reader) (err error) {
521
+
*t = GraphFollow{}
522
+
523
+
cr := cbg.NewCborReader(r)
524
+
525
+
maj, extra, err := cr.ReadHeader()
526
+
if err != nil {
527
+
return err
528
+
}
529
+
defer func() {
530
+
if err == io.EOF {
531
+
err = io.ErrUnexpectedEOF
532
+
}
533
+
}()
534
+
535
+
if maj != cbg.MajMap {
536
+
return fmt.Errorf("cbor input should be of type map")
537
+
}
538
+
539
+
if extra > cbg.MaxLength {
540
+
return fmt.Errorf("GraphFollow: map struct too large (%d)", extra)
541
+
}
542
+
543
+
n := extra
544
+
545
+
nameBuf := make([]byte, 9)
546
+
for i := uint64(0); i < n; i++ {
547
+
nameLen, ok, err := cbg.ReadFullStringIntoBuf(cr, nameBuf, 1000000)
548
+
if err != nil {
549
+
return err
550
+
}
551
+
552
+
if !ok {
553
+
// Field doesn't exist on this type, so ignore it
554
+
if err := cbg.ScanForLinks(cr, func(cid.Cid) {}); err != nil {
555
+
return err
556
+
}
557
+
continue
558
+
}
559
+
560
+
switch string(nameBuf[:nameLen]) {
561
+
// t.LexiconTypeID (string) (string)
562
+
case "$type":
563
+
564
+
{
565
+
sval, err := cbg.ReadStringWithMax(cr, 1000000)
566
+
if err != nil {
567
+
return err
568
+
}
569
+
570
+
t.LexiconTypeID = string(sval)
571
+
}
572
+
// t.Subject (string) (string)
573
+
case "subject":
574
+
575
+
{
576
+
sval, err := cbg.ReadStringWithMax(cr, 1000000)
577
+
if err != nil {
578
+
return err
579
+
}
580
+
581
+
t.Subject = string(sval)
582
+
}
583
+
// t.CreatedAt (string) (string)
584
+
case "createdAt":
585
+
586
+
{
587
+
sval, err := cbg.ReadStringWithMax(cr, 1000000)
588
+
if err != nil {
589
+
return err
590
+
}
591
+
592
+
t.CreatedAt = string(sval)
593
+
}
594
+
595
+
default:
596
+
// Field doesn't exist on this type, so ignore it
597
+
if err := cbg.ScanForLinks(r, func(cid.Cid) {}); err != nil {
598
+
return err
599
+
}
600
+
}
601
+
}
602
+
603
+
return nil
604
+
}
+23
api/tangled/graphfollow.go
+23
api/tangled/graphfollow.go
···
···
1
+
// Code generated by cmd/lexgen (see Makefile's lexgen); DO NOT EDIT.
2
+
3
+
package tangled
4
+
5
+
// schema: sh.tangled.graph.follow
6
+
7
+
import (
8
+
"github.com/bluesky-social/indigo/lex/util"
9
+
)
10
+
11
+
const (
12
+
GraphFollowNSID = "sh.tangled.graph.follow"
13
+
)
14
+
15
+
func init() {
16
+
util.RegisterType("sh.tangled.graph.follow", &GraphFollow{})
17
+
} //
18
+
// RECORDTYPE: GraphFollow
19
+
type GraphFollow struct {
20
+
LexiconTypeID string `json:"$type,const=sh.tangled.graph.follow" cborgen:"$type,const=sh.tangled.graph.follow"`
21
+
CreatedAt string `json:"createdAt" cborgen:"createdAt"`
22
+
Subject string `json:"subject" cborgen:"subject"`
23
+
}
+7
appview/db/db.go
+7
appview/db/db.go
···
40
created text not null default (strftime('%Y-%m-%dT%H:%M:%SZ', 'now')),
41
unique(did, name, knot)
42
);
43
+
create table if not exists follows (
44
+
user_did text not null,
45
+
subject_did text not null,
46
+
followed_at text not null default (strftime('%Y-%m-%dT%H:%M:%SZ', 'now')),
47
+
primary key (user_did, subject_did),
48
+
check (user_did <> subject_did)
49
+
);
50
`)
51
if err != nil {
52
return nil, err
+7
appview/db/follow.go
+7
appview/db/follow.go
+76
appview/state/settings.go
+76
appview/state/settings.go
···
···
1
+
package state
2
+
3
+
import (
4
+
"log"
5
+
"net/http"
6
+
"strings"
7
+
"time"
8
+
9
+
comatproto "github.com/bluesky-social/indigo/api/atproto"
10
+
lexutil "github.com/bluesky-social/indigo/lex/util"
11
+
"github.com/gliderlabs/ssh"
12
+
"github.com/sotangled/tangled/api/tangled"
13
+
"github.com/sotangled/tangled/appview/pages"
14
+
)
15
+
16
+
func (s *State) Settings(w http.ResponseWriter, r *http.Request) {
17
+
// for now, this is just pubkeys
18
+
user := s.auth.GetUser(r)
19
+
pubKeys, err := s.db.GetPublicKeys(user.Did)
20
+
if err != nil {
21
+
log.Println(err)
22
+
}
23
+
24
+
s.pages.Settings(w, pages.SettingsParams{
25
+
LoggedInUser: user,
26
+
PubKeys: pubKeys,
27
+
})
28
+
}
29
+
30
+
func (s *State) SettingsKeys(w http.ResponseWriter, r *http.Request) {
31
+
switch r.Method {
32
+
case http.MethodGet:
33
+
w.Write([]byte("unimplemented"))
34
+
log.Println("unimplemented")
35
+
return
36
+
case http.MethodPut:
37
+
did := s.auth.GetDid(r)
38
+
key := r.FormValue("key")
39
+
key = strings.TrimSpace(key)
40
+
name := r.FormValue("name")
41
+
client, _ := s.auth.AuthorizedClient(r)
42
+
43
+
_, _, _, _, err := ssh.ParseAuthorizedKey([]byte(key))
44
+
if err != nil {
45
+
log.Printf("parsing public key: %s", err)
46
+
return
47
+
}
48
+
49
+
if err := s.db.AddPublicKey(did, name, key); err != nil {
50
+
log.Printf("adding public key: %s", err)
51
+
return
52
+
}
53
+
54
+
// store in pds too
55
+
resp, err := comatproto.RepoPutRecord(r.Context(), client, &comatproto.RepoPutRecord_Input{
56
+
Collection: tangled.PublicKeyNSID,
57
+
Repo: did,
58
+
Rkey: s.TID(),
59
+
Record: &lexutil.LexiconTypeDecoder{
60
+
Val: &tangled.PublicKey{
61
+
Created: time.Now().Format(time.RFC3339),
62
+
Key: key,
63
+
Name: name,
64
+
}},
65
+
})
66
+
// invalid record
67
+
if err != nil {
68
+
log.Printf("failed to create record: %s", err)
69
+
return
70
+
}
71
+
72
+
log.Println("created atproto record: ", resp.Uri)
73
+
74
+
return
75
+
}
76
+
}
+42
-63
appview/state/state.go
+42
-63
appview/state/state.go
···
14
comatproto "github.com/bluesky-social/indigo/api/atproto"
15
"github.com/bluesky-social/indigo/atproto/syntax"
16
lexutil "github.com/bluesky-social/indigo/lex/util"
17
-
"github.com/gliderlabs/ssh"
18
"github.com/go-chi/chi/v5"
19
tangled "github.com/sotangled/tangled/api/tangled"
20
"github.com/sotangled/tangled/appview"
···
154
}
155
}
156
157
-
func (s *State) Settings(w http.ResponseWriter, r *http.Request) {
158
-
// for now, this is just pubkeys
159
-
user := s.auth.GetUser(r)
160
-
pubKeys, err := s.db.GetPublicKeys(user.Did)
161
-
if err != nil {
162
-
log.Println(err)
163
-
}
164
-
165
-
s.pages.Settings(w, pages.SettingsParams{
166
-
LoggedInUser: user,
167
-
PubKeys: pubKeys,
168
-
})
169
-
}
170
-
171
func (s *State) Keys(w http.ResponseWriter, r *http.Request) {
172
user := chi.URLParam(r, "user")
173
user = strings.TrimPrefix(user, "@")
···
197
for _, k := range pubKeys {
198
key := strings.TrimRight(k.Key, "\n")
199
w.Write([]byte(fmt.Sprintln(key)))
200
-
}
201
-
}
202
-
203
-
func (s *State) SettingsKeys(w http.ResponseWriter, r *http.Request) {
204
-
switch r.Method {
205
-
case http.MethodGet:
206
-
w.Write([]byte("unimplemented"))
207
-
log.Println("unimplemented")
208
-
return
209
-
case http.MethodPut:
210
-
did := s.auth.GetDid(r)
211
-
key := r.FormValue("key")
212
-
key = strings.TrimSpace(key)
213
-
name := r.FormValue("name")
214
-
client, _ := s.auth.AuthorizedClient(r)
215
-
216
-
_, _, _, _, err := ssh.ParseAuthorizedKey([]byte(key))
217
-
if err != nil {
218
-
log.Printf("parsing public key: %s", err)
219
-
return
220
-
}
221
-
222
-
if err := s.db.AddPublicKey(did, name, key); err != nil {
223
-
log.Printf("adding public key: %s", err)
224
-
return
225
-
}
226
-
227
-
// store in pds too
228
-
resp, err := comatproto.RepoPutRecord(r.Context(), client, &comatproto.RepoPutRecord_Input{
229
-
Collection: tangled.PublicKeyNSID,
230
-
Repo: did,
231
-
Rkey: s.TID(),
232
-
Record: &lexutil.LexiconTypeDecoder{
233
-
Val: &tangled.PublicKey{
234
-
Created: time.Now().Format(time.RFC3339),
235
-
Key: key,
236
-
Name: name,
237
-
}},
238
-
})
239
-
// invalid record
240
-
if err != nil {
241
-
log.Printf("failed to create record: %s", err)
242
-
return
243
-
}
244
-
245
-
log.Println("created atproto record: ", resp.Uri)
246
-
247
-
return
248
}
249
}
250
···
594
})
595
}
596
597
func (s *State) Router() http.Handler {
598
router := chi.NewRouter()
599
···
670
})
671
// r.Post("/import", s.ImportRepo)
672
})
673
674
r.Route("/settings", func(r chi.Router) {
675
r.Use(AuthMiddleware(s))
···
14
comatproto "github.com/bluesky-social/indigo/api/atproto"
15
"github.com/bluesky-social/indigo/atproto/syntax"
16
lexutil "github.com/bluesky-social/indigo/lex/util"
17
"github.com/go-chi/chi/v5"
18
tangled "github.com/sotangled/tangled/api/tangled"
19
"github.com/sotangled/tangled/appview"
···
153
}
154
}
155
156
func (s *State) Keys(w http.ResponseWriter, r *http.Request) {
157
user := chi.URLParam(r, "user")
158
user = strings.TrimPrefix(user, "@")
···
182
for _, k := range pubKeys {
183
key := strings.TrimRight(k.Key, "\n")
184
w.Write([]byte(fmt.Sprintln(key)))
185
}
186
}
187
···
531
})
532
}
533
534
+
func (s *State) Follow(w http.ResponseWriter, r *http.Request) {
535
+
subject := r.FormValue("subject")
536
+
537
+
if subject == "" {
538
+
log.Println("invalid form")
539
+
return
540
+
}
541
+
542
+
subjectIdent, err := s.resolver.ResolveIdent(r.Context(), subject)
543
+
currentUser := s.auth.GetUser(r)
544
+
545
+
client, _ := s.auth.AuthorizedClient(r)
546
+
createdAt := time.Now().Format(time.RFC3339)
547
+
resp, err := comatproto.RepoPutRecord(r.Context(), client, &comatproto.RepoPutRecord_Input{
548
+
Collection: tangled.GraphFollowNSID,
549
+
Repo: currentUser.Did,
550
+
Rkey: s.TID(),
551
+
Record: &lexutil.LexiconTypeDecoder{
552
+
Val: &tangled.GraphFollow{
553
+
Subject: subjectIdent.DID.String(),
554
+
CreatedAt: createdAt,
555
+
}},
556
+
})
557
+
558
+
err = s.db.AddFollow(currentUser.Did, subjectIdent.DID.String())
559
+
if err != nil {
560
+
log.Println("failed to follow", err)
561
+
return
562
+
}
563
+
564
+
// invalid record
565
+
if err != nil {
566
+
log.Printf("failed to create record: %s", err)
567
+
return
568
+
}
569
+
log.Println("created atproto record: ", resp.Uri)
570
+
571
+
return
572
+
}
573
+
574
func (s *State) Router() http.Handler {
575
router := chi.NewRouter()
576
···
647
})
648
// r.Post("/import", s.ImportRepo)
649
})
650
+
651
+
r.With(AuthMiddleware(s)).Put("/follow", s.Follow)
652
653
r.Route("/settings", func(r chi.Router) {
654
r.Use(AuthMiddleware(s))
+1
cmd/gen.go
+1
cmd/gen.go
+30
lexicons/follow.json
+30
lexicons/follow.json
···
···
1
+
{
2
+
"lexicon": 1,
3
+
"id": "sh.tangled.graph.follow",
4
+
"needsCbor": true,
5
+
"needsType": true,
6
+
"defs": {
7
+
"main": {
8
+
"type": "record",
9
+
"key": "tid",
10
+
"record": {
11
+
"type": "object",
12
+
"required": [
13
+
"createdAt",
14
+
"subject"
15
+
],
16
+
"properties": {
17
+
"createdAt": {
18
+
"type": "string",
19
+
"format": "datetime"
20
+
},
21
+
"subject": {
22
+
"type": "string",
23
+
"format": "did"
24
+
}
25
+
}
26
+
}
27
+
}
28
+
}
29
+
}
30
+