forked from tangled.org/core
this repo has no description

add /follow endpoint

Changed files
+350 -63
api
appview
cmd
lexicons
+164
api/tangled/cbor_gen.go
··· 438 439 return nil 440 }
··· 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
···
··· 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
··· 40 created text not null default (strftime('%Y-%m-%dT%H:%M:%SZ', 'now')), 41 unique(did, name, knot) 42 ); 43 `) 44 if err != nil { 45 return nil, err
··· 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
···
··· 1 + package db 2 + 3 + func (d *DB) AddFollow(userDid, subjectDid string) error { 4 + query := `insert into follows (user_did, subject_did) values (?, ?)` 5 + _, err := d.db.Exec(query, userDid, subjectDid) 6 + return err 7 + }
+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
··· 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
··· 16 "tangled", 17 shtangled.PublicKey{}, 18 shtangled.KnotMember{}, 19 ); err != nil { 20 panic(err) 21 }
··· 16 "tangled", 17 shtangled.PublicKey{}, 18 shtangled.KnotMember{}, 19 + shtangled.GraphFollow{}, 20 ); err != nil { 21 panic(err) 22 }
+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 +