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
···
return nil
}
+
func (t *GraphFollow) MarshalCBOR(w io.Writer) error {
+
if t == nil {
+
_, err := w.Write(cbg.CborNull)
+
return err
+
}
+
+
cw := cbg.NewCborWriter(w)
+
+
if _, err := cw.Write([]byte{163}); err != nil {
+
return err
+
}
+
+
// t.LexiconTypeID (string) (string)
+
if len("$type") > 1000000 {
+
return xerrors.Errorf("Value in field \"$type\" was too long")
+
}
+
+
if err := cw.WriteMajorTypeHeader(cbg.MajTextString, uint64(len("$type"))); err != nil {
+
return err
+
}
+
if _, err := cw.WriteString(string("$type")); err != nil {
+
return err
+
}
+
+
if err := cw.WriteMajorTypeHeader(cbg.MajTextString, uint64(len("sh.tangled.graph.follow"))); err != nil {
+
return err
+
}
+
if _, err := cw.WriteString(string("sh.tangled.graph.follow")); err != nil {
+
return err
+
}
+
+
// t.Subject (string) (string)
+
if len("subject") > 1000000 {
+
return xerrors.Errorf("Value in field \"subject\" was too long")
+
}
+
+
if err := cw.WriteMajorTypeHeader(cbg.MajTextString, uint64(len("subject"))); err != nil {
+
return err
+
}
+
if _, err := cw.WriteString(string("subject")); err != nil {
+
return err
+
}
+
+
if len(t.Subject) > 1000000 {
+
return xerrors.Errorf("Value in field t.Subject was too long")
+
}
+
+
if err := cw.WriteMajorTypeHeader(cbg.MajTextString, uint64(len(t.Subject))); err != nil {
+
return err
+
}
+
if _, err := cw.WriteString(string(t.Subject)); err != nil {
+
return err
+
}
+
+
// t.CreatedAt (string) (string)
+
if len("createdAt") > 1000000 {
+
return xerrors.Errorf("Value in field \"createdAt\" was too long")
+
}
+
+
if err := cw.WriteMajorTypeHeader(cbg.MajTextString, uint64(len("createdAt"))); err != nil {
+
return err
+
}
+
if _, err := cw.WriteString(string("createdAt")); err != nil {
+
return err
+
}
+
+
if len(t.CreatedAt) > 1000000 {
+
return xerrors.Errorf("Value in field t.CreatedAt was too long")
+
}
+
+
if err := cw.WriteMajorTypeHeader(cbg.MajTextString, uint64(len(t.CreatedAt))); err != nil {
+
return err
+
}
+
if _, err := cw.WriteString(string(t.CreatedAt)); err != nil {
+
return err
+
}
+
return nil
+
}
+
+
func (t *GraphFollow) UnmarshalCBOR(r io.Reader) (err error) {
+
*t = GraphFollow{}
+
+
cr := cbg.NewCborReader(r)
+
+
maj, extra, err := cr.ReadHeader()
+
if err != nil {
+
return err
+
}
+
defer func() {
+
if err == io.EOF {
+
err = io.ErrUnexpectedEOF
+
}
+
}()
+
+
if maj != cbg.MajMap {
+
return fmt.Errorf("cbor input should be of type map")
+
}
+
+
if extra > cbg.MaxLength {
+
return fmt.Errorf("GraphFollow: map struct too large (%d)", extra)
+
}
+
+
n := extra
+
+
nameBuf := make([]byte, 9)
+
for i := uint64(0); i < n; i++ {
+
nameLen, ok, err := cbg.ReadFullStringIntoBuf(cr, nameBuf, 1000000)
+
if err != nil {
+
return err
+
}
+
+
if !ok {
+
// Field doesn't exist on this type, so ignore it
+
if err := cbg.ScanForLinks(cr, func(cid.Cid) {}); err != nil {
+
return err
+
}
+
continue
+
}
+
+
switch string(nameBuf[:nameLen]) {
+
// t.LexiconTypeID (string) (string)
+
case "$type":
+
+
{
+
sval, err := cbg.ReadStringWithMax(cr, 1000000)
+
if err != nil {
+
return err
+
}
+
+
t.LexiconTypeID = string(sval)
+
}
+
// t.Subject (string) (string)
+
case "subject":
+
+
{
+
sval, err := cbg.ReadStringWithMax(cr, 1000000)
+
if err != nil {
+
return err
+
}
+
+
t.Subject = string(sval)
+
}
+
// t.CreatedAt (string) (string)
+
case "createdAt":
+
+
{
+
sval, err := cbg.ReadStringWithMax(cr, 1000000)
+
if err != nil {
+
return err
+
}
+
+
t.CreatedAt = string(sval)
+
}
+
+
default:
+
// Field doesn't exist on this type, so ignore it
+
if err := cbg.ScanForLinks(r, func(cid.Cid) {}); err != nil {
+
return err
+
}
+
}
+
}
+
+
return nil
+
}
+23
api/tangled/graphfollow.go
···
+
// Code generated by cmd/lexgen (see Makefile's lexgen); DO NOT EDIT.
+
+
package tangled
+
+
// schema: sh.tangled.graph.follow
+
+
import (
+
"github.com/bluesky-social/indigo/lex/util"
+
)
+
+
const (
+
GraphFollowNSID = "sh.tangled.graph.follow"
+
)
+
+
func init() {
+
util.RegisterType("sh.tangled.graph.follow", &GraphFollow{})
+
} //
+
// RECORDTYPE: GraphFollow
+
type GraphFollow struct {
+
LexiconTypeID string `json:"$type,const=sh.tangled.graph.follow" cborgen:"$type,const=sh.tangled.graph.follow"`
+
CreatedAt string `json:"createdAt" cborgen:"createdAt"`
+
Subject string `json:"subject" cborgen:"subject"`
+
}
+7
appview/db/db.go
···
created text not null default (strftime('%Y-%m-%dT%H:%M:%SZ', 'now')),
unique(did, name, knot)
);
+
create table if not exists follows (
+
user_did text not null,
+
subject_did text not null,
+
followed_at text not null default (strftime('%Y-%m-%dT%H:%M:%SZ', 'now')),
+
primary key (user_did, subject_did),
+
check (user_did <> subject_did)
+
);
`)
if err != nil {
return nil, err
+7
appview/db/follow.go
···
+
package db
+
+
func (d *DB) AddFollow(userDid, subjectDid string) error {
+
query := `insert into follows (user_did, subject_did) values (?, ?)`
+
_, err := d.db.Exec(query, userDid, subjectDid)
+
return err
+
}
+76
appview/state/settings.go
···
+
package state
+
+
import (
+
"log"
+
"net/http"
+
"strings"
+
"time"
+
+
comatproto "github.com/bluesky-social/indigo/api/atproto"
+
lexutil "github.com/bluesky-social/indigo/lex/util"
+
"github.com/gliderlabs/ssh"
+
"github.com/sotangled/tangled/api/tangled"
+
"github.com/sotangled/tangled/appview/pages"
+
)
+
+
func (s *State) Settings(w http.ResponseWriter, r *http.Request) {
+
// for now, this is just pubkeys
+
user := s.auth.GetUser(r)
+
pubKeys, err := s.db.GetPublicKeys(user.Did)
+
if err != nil {
+
log.Println(err)
+
}
+
+
s.pages.Settings(w, pages.SettingsParams{
+
LoggedInUser: user,
+
PubKeys: pubKeys,
+
})
+
}
+
+
func (s *State) SettingsKeys(w http.ResponseWriter, r *http.Request) {
+
switch r.Method {
+
case http.MethodGet:
+
w.Write([]byte("unimplemented"))
+
log.Println("unimplemented")
+
return
+
case http.MethodPut:
+
did := s.auth.GetDid(r)
+
key := r.FormValue("key")
+
key = strings.TrimSpace(key)
+
name := r.FormValue("name")
+
client, _ := s.auth.AuthorizedClient(r)
+
+
_, _, _, _, err := ssh.ParseAuthorizedKey([]byte(key))
+
if err != nil {
+
log.Printf("parsing public key: %s", err)
+
return
+
}
+
+
if err := s.db.AddPublicKey(did, name, key); err != nil {
+
log.Printf("adding public key: %s", err)
+
return
+
}
+
+
// store in pds too
+
resp, err := comatproto.RepoPutRecord(r.Context(), client, &comatproto.RepoPutRecord_Input{
+
Collection: tangled.PublicKeyNSID,
+
Repo: did,
+
Rkey: s.TID(),
+
Record: &lexutil.LexiconTypeDecoder{
+
Val: &tangled.PublicKey{
+
Created: time.Now().Format(time.RFC3339),
+
Key: key,
+
Name: name,
+
}},
+
})
+
// invalid record
+
if err != nil {
+
log.Printf("failed to create record: %s", err)
+
return
+
}
+
+
log.Println("created atproto record: ", resp.Uri)
+
+
return
+
}
+
}
+42 -63
appview/state/state.go
···
comatproto "github.com/bluesky-social/indigo/api/atproto"
"github.com/bluesky-social/indigo/atproto/syntax"
lexutil "github.com/bluesky-social/indigo/lex/util"
-
"github.com/gliderlabs/ssh"
"github.com/go-chi/chi/v5"
tangled "github.com/sotangled/tangled/api/tangled"
"github.com/sotangled/tangled/appview"
···
}
}
-
func (s *State) Settings(w http.ResponseWriter, r *http.Request) {
-
// for now, this is just pubkeys
-
user := s.auth.GetUser(r)
-
pubKeys, err := s.db.GetPublicKeys(user.Did)
-
if err != nil {
-
log.Println(err)
-
}
-
-
s.pages.Settings(w, pages.SettingsParams{
-
LoggedInUser: user,
-
PubKeys: pubKeys,
-
})
-
}
-
func (s *State) Keys(w http.ResponseWriter, r *http.Request) {
user := chi.URLParam(r, "user")
user = strings.TrimPrefix(user, "@")
···
for _, k := range pubKeys {
key := strings.TrimRight(k.Key, "\n")
w.Write([]byte(fmt.Sprintln(key)))
-
}
-
}
-
-
func (s *State) SettingsKeys(w http.ResponseWriter, r *http.Request) {
-
switch r.Method {
-
case http.MethodGet:
-
w.Write([]byte("unimplemented"))
-
log.Println("unimplemented")
-
return
-
case http.MethodPut:
-
did := s.auth.GetDid(r)
-
key := r.FormValue("key")
-
key = strings.TrimSpace(key)
-
name := r.FormValue("name")
-
client, _ := s.auth.AuthorizedClient(r)
-
-
_, _, _, _, err := ssh.ParseAuthorizedKey([]byte(key))
-
if err != nil {
-
log.Printf("parsing public key: %s", err)
-
return
-
}
-
-
if err := s.db.AddPublicKey(did, name, key); err != nil {
-
log.Printf("adding public key: %s", err)
-
return
-
}
-
-
// store in pds too
-
resp, err := comatproto.RepoPutRecord(r.Context(), client, &comatproto.RepoPutRecord_Input{
-
Collection: tangled.PublicKeyNSID,
-
Repo: did,
-
Rkey: s.TID(),
-
Record: &lexutil.LexiconTypeDecoder{
-
Val: &tangled.PublicKey{
-
Created: time.Now().Format(time.RFC3339),
-
Key: key,
-
Name: name,
-
}},
-
})
-
// invalid record
-
if err != nil {
-
log.Printf("failed to create record: %s", err)
-
return
-
}
-
-
log.Println("created atproto record: ", resp.Uri)
-
-
return
}
}
···
})
}
+
func (s *State) Follow(w http.ResponseWriter, r *http.Request) {
+
subject := r.FormValue("subject")
+
+
if subject == "" {
+
log.Println("invalid form")
+
return
+
}
+
+
subjectIdent, err := s.resolver.ResolveIdent(r.Context(), subject)
+
currentUser := s.auth.GetUser(r)
+
+
client, _ := s.auth.AuthorizedClient(r)
+
createdAt := time.Now().Format(time.RFC3339)
+
resp, err := comatproto.RepoPutRecord(r.Context(), client, &comatproto.RepoPutRecord_Input{
+
Collection: tangled.GraphFollowNSID,
+
Repo: currentUser.Did,
+
Rkey: s.TID(),
+
Record: &lexutil.LexiconTypeDecoder{
+
Val: &tangled.GraphFollow{
+
Subject: subjectIdent.DID.String(),
+
CreatedAt: createdAt,
+
}},
+
})
+
+
err = s.db.AddFollow(currentUser.Did, subjectIdent.DID.String())
+
if err != nil {
+
log.Println("failed to follow", err)
+
return
+
}
+
+
// invalid record
+
if err != nil {
+
log.Printf("failed to create record: %s", err)
+
return
+
}
+
log.Println("created atproto record: ", resp.Uri)
+
+
return
+
}
+
func (s *State) Router() http.Handler {
router := chi.NewRouter()
···
})
// r.Post("/import", s.ImportRepo)
})
+
+
r.With(AuthMiddleware(s)).Put("/follow", s.Follow)
r.Route("/settings", func(r chi.Router) {
r.Use(AuthMiddleware(s))
+1
cmd/gen.go
···
"tangled",
shtangled.PublicKey{},
shtangled.KnotMember{},
+
shtangled.GraphFollow{},
); err != nil {
panic(err)
}
+30
lexicons/follow.json
···
+
{
+
"lexicon": 1,
+
"id": "sh.tangled.graph.follow",
+
"needsCbor": true,
+
"needsType": true,
+
"defs": {
+
"main": {
+
"type": "record",
+
"key": "tid",
+
"record": {
+
"type": "object",
+
"required": [
+
"createdAt",
+
"subject"
+
],
+
"properties": {
+
"createdAt": {
+
"type": "string",
+
"format": "datetime"
+
},
+
"subject": {
+
"type": "string",
+
"format": "did"
+
}
+
}
+
}
+
}
+
}
+
}
+