diff --git a/api/tangled/cbor_gen.go b/api/tangled/cbor_gen.go index 23837d71..3e565f70 100644 --- a/api/tangled/cbor_gen.go +++ b/api/tangled/cbor_gen.go @@ -2460,6 +2460,228 @@ func (t *GraphFollow) UnmarshalCBOR(r io.Reader) (err error) { return nil } +func (t *GraphVouch) MarshalCBOR(w io.Writer) error { + if t == nil { + _, err := w.Write(cbg.CborNull) + return err + } + + cw := cbg.NewCborWriter(w) + fieldCount := 4 + + if t.Reason == nil { + fieldCount-- + } + + if _, err := cw.Write(cbg.CborEncodeMajorType(cbg.MajMap, uint64(fieldCount))); err != nil { + return err + } + + // t.Kind (string) (string) + if len("kind") > 1000000 { + return xerrors.Errorf("Value in field \"kind\" was too long") + } + + if err := cw.WriteMajorTypeHeader(cbg.MajTextString, uint64(len("kind"))); err != nil { + return err + } + if _, err := cw.WriteString(string("kind")); err != nil { + return err + } + + if len(t.Kind) > 1000000 { + return xerrors.Errorf("Value in field t.Kind was too long") + } + + if err := cw.WriteMajorTypeHeader(cbg.MajTextString, uint64(len(t.Kind))); err != nil { + return err + } + if _, err := cw.WriteString(string(t.Kind)); 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.vouch"))); err != nil { + return err + } + if _, err := cw.WriteString(string("sh.tangled.graph.vouch")); err != nil { + return err + } + + // t.Reason (string) (string) + if t.Reason != nil { + + if len("reason") > 1000000 { + return xerrors.Errorf("Value in field \"reason\" was too long") + } + + if err := cw.WriteMajorTypeHeader(cbg.MajTextString, uint64(len("reason"))); err != nil { + return err + } + if _, err := cw.WriteString(string("reason")); err != nil { + return err + } + + if t.Reason == nil { + if _, err := cw.Write(cbg.CborNull); err != nil { + return err + } + } else { + if len(*t.Reason) > 1000000 { + return xerrors.Errorf("Value in field t.Reason was too long") + } + + if err := cw.WriteMajorTypeHeader(cbg.MajTextString, uint64(len(*t.Reason))); err != nil { + return err + } + if _, err := cw.WriteString(string(*t.Reason)); 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 *GraphVouch) UnmarshalCBOR(r io.Reader) (err error) { + *t = GraphVouch{} + + 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("GraphVouch: 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.Kind (string) (string) + case "kind": + + { + sval, err := cbg.ReadStringWithMax(cr, 1000000) + if err != nil { + return err + } + + t.Kind = string(sval) + } + // t.LexiconTypeID (string) (string) + case "$type": + + { + sval, err := cbg.ReadStringWithMax(cr, 1000000) + if err != nil { + return err + } + + t.LexiconTypeID = string(sval) + } + // t.Reason (string) (string) + case "reason": + + { + b, err := cr.ReadByte() + if err != nil { + return err + } + if b != cbg.CborNull[0] { + if err := cr.UnreadByte(); err != nil { + return err + } + + sval, err := cbg.ReadStringWithMax(cr, 1000000) + if err != nil { + return err + } + + t.Reason = (*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 +} func (t *Knot) MarshalCBOR(w io.Writer) error { if t == nil { _, err := w.Write(cbg.CborNull) @@ -9520,37 +9742,173 @@ func (t *RepoPullComment) UnmarshalCBOR(r io.Reader) (err error) { return nil } -func (t *RepoPull_Source) MarshalCBOR(w io.Writer) error { +func (t *RepoPull_Round) MarshalCBOR(w io.Writer) error { if t == nil { _, err := w.Write(cbg.CborNull) return err } cw := cbg.NewCborWriter(w) - fieldCount := 3 - if t.Repo == nil { - fieldCount-- + if _, err := cw.Write([]byte{162}); err != nil { + return err } - if t.RepoDid == nil { - fieldCount-- + // t.CreatedAt (string) (string) + if len("createdAt") > 1000000 { + return xerrors.Errorf("Value in field \"createdAt\" was too long") } - if _, err := cw.Write(cbg.CborEncodeMajorType(cbg.MajMap, uint64(fieldCount))); err != nil { + if err := cw.WriteMajorTypeHeader(cbg.MajTextString, uint64(len("createdAt"))); err != nil { + return err + } + if _, err := cw.WriteString(string("createdAt")); err != nil { return err } - // t.Repo (string) (string) - if t.Repo != nil { + if len(t.CreatedAt) > 1000000 { + return xerrors.Errorf("Value in field t.CreatedAt was too long") + } - if len("repo") > 1000000 { - return xerrors.Errorf("Value in field \"repo\" 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 + } - if err := cw.WriteMajorTypeHeader(cbg.MajTextString, uint64(len("repo"))); err != nil { - return err - } + // t.PatchBlob (util.LexBlob) (struct) + if len("patchBlob") > 1000000 { + return xerrors.Errorf("Value in field \"patchBlob\" was too long") + } + + if err := cw.WriteMajorTypeHeader(cbg.MajTextString, uint64(len("patchBlob"))); err != nil { + return err + } + if _, err := cw.WriteString(string("patchBlob")); err != nil { + return err + } + + if err := t.PatchBlob.MarshalCBOR(cw); err != nil { + return err + } + return nil +} + +func (t *RepoPull_Round) UnmarshalCBOR(r io.Reader) (err error) { + *t = RepoPull_Round{} + + 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("RepoPull_Round: 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.CreatedAt (string) (string) + case "createdAt": + + { + sval, err := cbg.ReadStringWithMax(cr, 1000000) + if err != nil { + return err + } + + t.CreatedAt = string(sval) + } + // t.PatchBlob (util.LexBlob) (struct) + case "patchBlob": + + { + + b, err := cr.ReadByte() + if err != nil { + return err + } + if b != cbg.CborNull[0] { + if err := cr.UnreadByte(); err != nil { + return err + } + t.PatchBlob = new(util.LexBlob) + if err := t.PatchBlob.UnmarshalCBOR(cr); err != nil { + return xerrors.Errorf("unmarshaling t.PatchBlob pointer: %w", err) + } + } + + } + + 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 +} +func (t *RepoPull_Source) MarshalCBOR(w io.Writer) error { + if t == nil { + _, err := w.Write(cbg.CborNull) + return err + } + + cw := cbg.NewCborWriter(w) + fieldCount := 3 + + if t.Repo == nil { + fieldCount-- + } + + if t.RepoDid == nil { + fieldCount-- + } + + if _, err := cw.Write(cbg.CborEncodeMajorType(cbg.MajMap, uint64(fieldCount))); err != nil { + return err + } + + // t.Repo (string) (string) + if t.Repo != nil { + + if len("repo") > 1000000 { + return xerrors.Errorf("Value in field \"repo\" was too long") + } + + if err := cw.WriteMajorTypeHeader(cbg.MajTextString, uint64(len("repo"))); err != nil { + return err + } if _, err := cw.WriteString(string("repo")); err != nil { return err } @@ -9735,142 +10093,6 @@ func (t *RepoPull_Source) UnmarshalCBOR(r io.Reader) (err error) { return nil } -func (t *RepoPull_Round) MarshalCBOR(w io.Writer) error { - if t == nil { - _, err := w.Write(cbg.CborNull) - return err - } - - cw := cbg.NewCborWriter(w) - - if _, err := cw.Write([]byte{162}); 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 - } - - // t.PatchBlob (util.LexBlob) (struct) - if len("patchBlob") > 1000000 { - return xerrors.Errorf("Value in field \"patchBlob\" was too long") - } - - if err := cw.WriteMajorTypeHeader(cbg.MajTextString, uint64(len("patchBlob"))); err != nil { - return err - } - if _, err := cw.WriteString(string("patchBlob")); err != nil { - return err - } - - if err := t.PatchBlob.MarshalCBOR(cw); err != nil { - return err - } - return nil -} - -func (t *RepoPull_Round) UnmarshalCBOR(r io.Reader) (err error) { - *t = RepoPull_Round{} - - 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("RepoPull_Round: 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.CreatedAt (string) (string) - case "createdAt": - - { - sval, err := cbg.ReadStringWithMax(cr, 1000000) - if err != nil { - return err - } - - t.CreatedAt = string(sval) - } - // t.PatchBlob (util.LexBlob) (struct) - case "patchBlob": - - { - - b, err := cr.ReadByte() - if err != nil { - return err - } - if b != cbg.CborNull[0] { - if err := cr.UnreadByte(); err != nil { - return err - } - t.PatchBlob = new(util.LexBlob) - if err := t.PatchBlob.UnmarshalCBOR(cr); err != nil { - return xerrors.Errorf("unmarshaling t.PatchBlob pointer: %w", err) - } - } - - } - - 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 -} func (t *RepoPullStatus) MarshalCBOR(w io.Writer) error { if t == nil { _, err := w.Write(cbg.CborNull) @@ -10810,248 +11032,3 @@ func (t *String) UnmarshalCBOR(r io.Reader) (err error) { return nil } -func (t *GraphVouch) MarshalCBOR(w io.Writer) error { - if t == nil { - _, err := w.Write(cbg.CborNull) - return err - } - - cw := cbg.NewCborWriter(w) - fieldCount := 4 - - if t.Kind == nil { - fieldCount-- - } - - if t.Reason == nil { - fieldCount-- - } - - if _, err := cw.Write(cbg.CborEncodeMajorType(cbg.MajMap, uint64(fieldCount))); err != nil { - return err - } - - // t.Kind (string) (string) - if t.Kind != nil { - - if len("kind") > 1000000 { - return xerrors.Errorf("Value in field \"kind\" was too long") - } - - if err := cw.WriteMajorTypeHeader(cbg.MajTextString, uint64(len("kind"))); err != nil { - return err - } - if _, err := cw.WriteString(string("kind")); err != nil { - return err - } - - if t.Kind == nil { - if _, err := cw.Write(cbg.CborNull); err != nil { - return err - } - } else { - if len(*t.Kind) > 1000000 { - return xerrors.Errorf("Value in field t.Kind was too long") - } - - if err := cw.WriteMajorTypeHeader(cbg.MajTextString, uint64(len(*t.Kind))); err != nil { - return err - } - if _, err := cw.WriteString(string(*t.Kind)); 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.vouch"))); err != nil { - return err - } - if _, err := cw.WriteString(string("sh.tangled.graph.vouch")); err != nil { - return err - } - - // t.Reason (string) (string) - if t.Reason != nil { - - if len("reason") > 1000000 { - return xerrors.Errorf("Value in field \"reason\" was too long") - } - - if err := cw.WriteMajorTypeHeader(cbg.MajTextString, uint64(len("reason"))); err != nil { - return err - } - if _, err := cw.WriteString(string("reason")); err != nil { - return err - } - - if t.Reason == nil { - if _, err := cw.Write(cbg.CborNull); err != nil { - return err - } - } else { - if len(*t.Reason) > 1000000 { - return xerrors.Errorf("Value in field t.Reason was too long") - } - - if err := cw.WriteMajorTypeHeader(cbg.MajTextString, uint64(len(*t.Reason))); err != nil { - return err - } - if _, err := cw.WriteString(string(*t.Reason)); 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 *GraphVouch) UnmarshalCBOR(r io.Reader) (err error) { - *t = GraphVouch{} - - 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("GraphVouch: 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.Kind (string) (string) - case "kind": - - { - b, err := cr.ReadByte() - if err != nil { - return err - } - if b != cbg.CborNull[0] { - if err := cr.UnreadByte(); err != nil { - return err - } - - sval, err := cbg.ReadStringWithMax(cr, 1000000) - if err != nil { - return err - } - - t.Kind = (*string)(&sval) - } - } - // t.LexiconTypeID (string) (string) - case "$type": - - { - sval, err := cbg.ReadStringWithMax(cr, 1000000) - if err != nil { - return err - } - - t.LexiconTypeID = string(sval) - } - // t.Reason (string) (string) - case "reason": - - { - b, err := cr.ReadByte() - if err != nil { - return err - } - if b != cbg.CborNull[0] { - if err := cr.UnreadByte(); err != nil { - return err - } - - sval, err := cbg.ReadStringWithMax(cr, 1000000) - if err != nil { - return err - } - - t.Reason = (*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 -} diff --git a/api/tangled/graphvouch.go b/api/tangled/graphvouch.go index 0569e32e..22d1d4c0 100644 --- a/api/tangled/graphvouch.go +++ b/api/tangled/graphvouch.go @@ -20,7 +20,7 @@ type GraphVouch struct { LexiconTypeID string `json:"$type,const=sh.tangled.graph.vouch" cborgen:"$type,const=sh.tangled.graph.vouch"` CreatedAt string `json:"createdAt" cborgen:"createdAt"` // kind: Whether this user is being vouched for or denounced - Kind *string `json:"kind,omitempty" cborgen:"kind,omitempty"` + Kind string `json:"kind" cborgen:"kind"` // reason: The reason for this vouch/denouncement Reason *string `json:"reason,omitempty" cborgen:"reason,omitempty"` } diff --git a/appview/db/db.go b/appview/db/db.go index 92fbfb69..82ba49cb 100644 --- a/appview/db/db.go +++ b/appview/db/db.go @@ -95,6 +95,7 @@ func Make(ctx context.Context, dbPath string) (*DB, error) { create table if not exists vouches ( did text not null, subject_did text not null, + cid text not null, kind text not null default 'vouch', reason text, created_at text not null default (strftime('%Y-%m-%dT%H:%M:%SZ', 'now')), diff --git a/appview/db/vouch.go b/appview/db/vouch.go index 25d22af1..37ede8f2 100644 --- a/appview/db/vouch.go +++ b/appview/db/vouch.go @@ -7,13 +7,14 @@ import ( "strings" "time" + "github.com/ipfs/go-cid" "tangled.org/core/appview/models" "tangled.org/core/orm" ) func AddVouch(e Execer, vouch *models.Vouch) error { - query := `insert or replace into vouches (did, subject_did, kind, reason) values (?, ?, ?, ?)` - _, err := e.Exec(query, vouch.Did, vouch.SubjectDid, vouch.Kind, vouch.Reason) + query := `insert or replace into vouches (did, subject_did, cid, kind, reason) values (?, ?, ?, ?, ?)` + _, err := e.Exec(query, vouch.Did, vouch.SubjectDid, vouch.Cid.String(), vouch.Kind, vouch.Reason) return err } @@ -49,7 +50,7 @@ func GetVouches(e Execer, limit int, filters ...orm.Filter) ([]models.Vouch, err } query := fmt.Sprintf( - `select did, subject_did, kind, reason, created_at + `select did, subject_did, cid, kind, reason, created_at from vouches %s order by created_at desc @@ -65,10 +66,12 @@ func GetVouches(e Execer, limit int, filters ...orm.Filter) ([]models.Vouch, err for rows.Next() { var vouch models.Vouch var createdAt string + var cidStr string var reason sql.NullString err := rows.Scan( &vouch.Did, &vouch.SubjectDid, + &cidStr, &vouch.Kind, &reason, &createdAt, @@ -76,6 +79,13 @@ func GetVouches(e Execer, limit int, filters ...orm.Filter) ([]models.Vouch, err if err != nil { return nil, err } + + vouch.Cid, err = cid.Parse(cidStr) + if err != nil { + log.Println("unable to parse CID:", err) + continue + } + createdAtTime, err := time.Parse(time.RFC3339, createdAt) if err != nil { log.Println("unable to determine created at time") @@ -101,23 +111,6 @@ func DeleteVouchByRkey(e Execer, did, rkey string) error { return err } -func GetVouchStats(e Execer, did string) (models.VouchStats, error) { - var vouches, denounces int64 - err := e.QueryRow( - `SELECT - COUNT(CASE WHEN kind = 'vouch' THEN 1 END) AS vouches, - COUNT(CASE WHEN kind = 'denounce' THEN 1 END) AS denounces - FROM vouches - WHERE subject_did = ?`, did).Scan(&vouches, &denounces) - if err != nil { - return models.VouchStats{}, err - } - return models.VouchStats{ - Vouches: vouches, - Denounces: denounces, - }, nil -} - func GetVouchStatsBatch(e Execer, dids []string) (map[string]models.VouchStats, error) { if len(dids) == 0 { return nil, nil @@ -181,16 +174,12 @@ func GetVouchesReceived(e Execer, did string) ([]models.Vouch, error) { return GetVouches(e, 0, orm.FilterEq("subject_did", did)) } -// GetNetworkVouchesForSubject returns vouches for subjectDid from people that viewerDid follows or vouches for func GetNetworkVouchesForSubject(e Execer, viewerDid, subjectDid string, limit int) ([]models.Vouch, error) { query := ` - select distinct v.did, v.subject_did, v.kind, v.reason, v.created_at + select distinct v.did, v.subject_did, v.cid, v.kind, v.reason, v.created_at from vouches v where v.subject_did = ? - and v.kind = 'vouch' and v.did in ( - select subject_did from follows where user_did = ? - union select subject_did from vouches where did = ? and kind = 'vouch' ) order by v.created_at desc @@ -212,10 +201,12 @@ func GetNetworkVouchesForSubject(e Execer, viewerDid, subjectDid string, limit i for rows.Next() { var vouch models.Vouch var createdAt string + var cidStr string var reason sql.NullString err := rows.Scan( &vouch.Did, &vouch.SubjectDid, + &cidStr, &vouch.Kind, &reason, &createdAt, @@ -223,6 +214,13 @@ func GetNetworkVouchesForSubject(e Execer, viewerDid, subjectDid string, limit i if err != nil { return nil, err } + + vouch.Cid, err = cid.Parse(cidStr) + if err != nil { + log.Println("unable to parse CID:", err) + continue + } + createdAtTime, err := time.Parse(time.RFC3339, createdAt) if err != nil { log.Println("unable to determine created at time") @@ -238,93 +236,27 @@ func GetNetworkVouchesForSubject(e Execer, viewerDid, subjectDid string, limit i return vouches, nil } -// GetNetworkDenouncesForSubject returns denounces for subjectDid from people that viewerDid follows or vouches for -func GetNetworkDenouncesForSubject(e Execer, viewerDid, subjectDid string, limit int) ([]models.Vouch, error) { - query := ` - select distinct v.did, v.subject_did, v.kind, v.reason, v.created_at - from vouches v - where v.subject_did = ? - and v.kind = 'denounce' - and v.did in ( - select subject_did from follows where user_did = ? - union - select subject_did from vouches where did = ? and kind = 'vouch' - ) - order by v.created_at desc - ` - args := []any{subjectDid, viewerDid, viewerDid} - - if limit > 0 { - query += " limit ?" - args = append(args, limit) +func GetVouchRelationship(e Execer, viewerDid, subjectDid string) (*models.VouchRelationship, error) { + relationship := &models.VouchRelationship{ + ViewerDid: viewerDid, + SubjectDid: subjectDid, + NetworkVouches: []models.Vouch{}, } - rows, err := e.Query(query, args...) - if err != nil { + // Get direct vouch from viewer to subject (if exists) + directVouch, err := GetVouch(e, viewerDid, subjectDid) + if err == nil { + relationship.NetworkVouches = append(relationship.NetworkVouches, *directVouch) + } else if err != sql.ErrNoRows { return nil, err } - defer rows.Close() - var vouches []models.Vouch - for rows.Next() { - var vouch models.Vouch - var createdAt string - var reason sql.NullString - err := rows.Scan( - &vouch.Did, - &vouch.SubjectDid, - &vouch.Kind, - &reason, - &createdAt, - ) - if err != nil { - return nil, err - } - createdAtTime, err := time.Parse(time.RFC3339, createdAt) - if err != nil { - log.Println("unable to determine created at time") - vouch.CreatedAt = time.Now() - } else { - vouch.CreatedAt = createdAtTime - } - if reason.Valid { - vouch.Reason = &reason.String - } - vouches = append(vouches, vouch) + networkVouches, err := GetNetworkVouchesForSubject(e, viewerDid, subjectDid, 0) + if err != nil { + return nil, err } - return vouches, nil -} -// CountNetworkVouchesForSubject returns count of vouches for subjectDid from viewerDid's network -func CountNetworkVouchesForSubject(e Execer, viewerDid, subjectDid string) (int64, error) { - var count int64 - err := e.QueryRow(` - select count(distinct v.did) - from vouches v - where v.subject_did = ? - and v.kind = 'vouch' - and v.did in ( - select subject_did from follows where user_did = ? - union - select subject_did from vouches where did = ? and kind = 'vouch' - ) - `, subjectDid, viewerDid, viewerDid).Scan(&count) - return count, err -} + relationship.NetworkVouches = append(relationship.NetworkVouches, networkVouches...) -// CountNetworkDenouncesForSubject returns count of denounces for subjectDid from viewerDid's network -func CountNetworkDenouncesForSubject(e Execer, viewerDid, subjectDid string) (int64, error) { - var count int64 - err := e.QueryRow(` - select count(distinct v.did) - from vouches v - where v.subject_did = ? - and v.kind = 'denounce' - and v.did in ( - select subject_did from follows where user_did = ? - union - select subject_did from vouches where did = ? and kind = 'vouch' - ) - `, subjectDid, viewerDid, viewerDid).Scan(&count) - return count, err + return relationship, nil } diff --git a/appview/ingester.go b/appview/ingester.go index 8a2c96e9..b84ea176 100644 --- a/appview/ingester.go +++ b/appview/ingester.go @@ -211,6 +211,7 @@ func (i *Ingester) ingestVouch(ctx context.Context, e *jmodels.Event) error { l := i.Logger.With("handler", "ingestVouch") l = l.With("nsid", e.Commit.Collection) + l.Info("ingesting vouch") switch e.Commit.Operation { case jmodels.CommitOperationCreate, jmodels.CommitOperationUpdate: @@ -245,19 +246,22 @@ func (i *Ingester) ingestVouch(ctx context.Context, e *jmodels.Event) error { return err } - kind := "vouch" - if record.Kind != nil { - kind = *record.Kind - } - - if kind != "vouch" && kind != "denounce" { + kind, err := models.ParseVouchKind(record.Kind) + if err != nil { l.Error("invalid kind", "kind", kind) return fmt.Errorf("invalid kind: %s", kind) } + recordCid, err := cid.Parse(e.Commit.CID) + if err != nil { + l.Error("invalid cid", "err", err, "cid", e.Commit.CID) + return fmt.Errorf("invalid cid: %w", err) + } + err = db.AddVouch(i.Db, &models.Vouch{ Did: did, SubjectDid: subjectDID, + Cid: recordCid, Kind: kind, Reason: record.Reason, }) diff --git a/appview/models/vouch.go b/appview/models/vouch.go index 1549217a..2d30aed7 100644 --- a/appview/models/vouch.go +++ b/appview/models/vouch.go @@ -1,16 +1,116 @@ package models -import "time" +import ( + "fmt" + "time" + + "github.com/ipfs/go-cid" +) + +type VouchKind string + +const ( + VouchKindVouch VouchKind = "vouch" + VouchKindDenounce VouchKind = "denounce" +) + +func ParseVouchKind(v string) (VouchKind, error) { + switch v { + case "vouch": + return VouchKindVouch, nil + case "denounce": + return VouchKindVouch, nil + default: + return VouchKindVouch, fmt.Errorf("invalid vouch kind: %s", v) + } +} type Vouch struct { Did string SubjectDid string - Kind string + Cid cid.Cid + Kind VouchKind Reason *string CreatedAt time.Time } +func (v Vouch) IsVouch() bool { + return v.Kind == VouchKindVouch +} + +func (v Vouch) IsDenounce() bool { + return v.Kind == VouchKindDenounce +} + type VouchStats struct { Vouches int64 Denounces int64 } + +type VouchRelationship struct { + ViewerDid string + SubjectDid string + + NetworkVouches []Vouch +} + +func (vr *VouchRelationship) IsDirectVouch() bool { + for _, v := range vr.NetworkVouches { + if v.Did == vr.ViewerDid && v.SubjectDid == vr.SubjectDid && v.Kind == VouchKindVouch { + return true + } + } + return false +} + +func (vr *VouchRelationship) IsDirectDenounce() bool { + for _, v := range vr.NetworkVouches { + if v.Did == vr.ViewerDid && v.SubjectDid == vr.SubjectDid && v.Kind == VouchKindDenounce { + return true + } + } + return false +} + +func (vr *VouchRelationship) IndirectVouches() []Vouch { + var indirectVouches []Vouch + for _, v := range vr.NetworkVouches { + if v.Did != vr.ViewerDid { + indirectVouches = append(indirectVouches, v) + } + } + return indirectVouches +} + +func (vr *VouchRelationship) IsEmpty() bool { + return len(vr.NetworkVouches) == 0 +} + +func (vr *VouchRelationship) GetDirectVouch() *Vouch { + for _, v := range vr.NetworkVouches { + if v.Did == vr.ViewerDid && v.SubjectDid == vr.SubjectDid { + return &v + } + } + return nil +} + +func (vr *VouchRelationship) VouchStrength() int { + count := 0 + for _, v := range vr.NetworkVouches { + if v.Did != vr.ViewerDid && v.Kind == VouchKindVouch { + count++ + } + } + return count +} + +func (vr *VouchRelationship) DenounceStrength() int { + count := 0 + for _, v := range vr.NetworkVouches { + if v.Did != vr.ViewerDid && v.Kind == VouchKindDenounce { + count++ + } + } + return count +} diff --git a/appview/oauth/scopes.go b/appview/oauth/scopes.go index 351e05d9..83397f86 100644 --- a/appview/oauth/scopes.go +++ b/appview/oauth/scopes.go @@ -3,39 +3,40 @@ package oauth var TangledScopes = []string{ "atproto", + "repo:sh.tangled.actor.profile", + "repo:sh.tangled.feed.reaction", + "repo:sh.tangled.feed.star", + "repo:sh.tangled.graph.follow", + "repo:sh.tangled.graph.vouch", + "repo:sh.tangled.knot", + "repo:sh.tangled.knot.member", + "repo:sh.tangled.label.definition", + "repo:sh.tangled.label.op", "repo:sh.tangled.publicKey", "repo:sh.tangled.repo", - "repo:sh.tangled.repo.pull", - "repo:sh.tangled.repo.pull.comment", "repo:sh.tangled.repo.artifact", + "repo:sh.tangled.repo.collaborator", "repo:sh.tangled.repo.issue", "repo:sh.tangled.repo.issue.comment", - "repo:sh.tangled.repo.collaborator", - "repo:sh.tangled.knot", - "repo:sh.tangled.knot.member", + "repo:sh.tangled.repo.pull", + "repo:sh.tangled.repo.pull.comment", "repo:sh.tangled.spindle", "repo:sh.tangled.spindle.member", - "repo:sh.tangled.graph.follow", - "repo:sh.tangled.feed.star", - "repo:sh.tangled.feed.reaction", - "repo:sh.tangled.label.definition", - "repo:sh.tangled.label.op", "repo:sh.tangled.string", - "repo:sh.tangled.actor.profile", "blob:*/*", + "rpc:sh.tangled.pipeline.cancelPipeline?aud=*", + "rpc:sh.tangled.repo.addSecret?aud=*", "rpc:sh.tangled.repo.create?aud=*", "rpc:sh.tangled.repo.delete?aud=*", - "rpc:sh.tangled.repo.merge?aud=*", - "rpc:sh.tangled.repo.hiddenRef?aud=*", "rpc:sh.tangled.repo.deleteBranch?aud=*", - "rpc:sh.tangled.repo.setDefaultBranch?aud=*", - "rpc:sh.tangled.repo.forkSync?aud=*", "rpc:sh.tangled.repo.forkStatus?aud=*", + "rpc:sh.tangled.repo.forkSync?aud=*", + "rpc:sh.tangled.repo.hiddenRef?aud=*", + "rpc:sh.tangled.repo.listSecrets?aud=*", + "rpc:sh.tangled.repo.merge?aud=*", "rpc:sh.tangled.repo.mergeCheck?aud=*", - "rpc:sh.tangled.pipeline.cancelPipeline?aud=*", - "rpc:sh.tangled.repo.addSecret?aud=*", "rpc:sh.tangled.repo.removeSecret?aud=*", - "rpc:sh.tangled.repo.listSecrets?aud=*", + "rpc:sh.tangled.repo.setDefaultBranch?aud=*", } diff --git a/appview/pages/pages.go b/appview/pages/pages.go index 8b1f6adc..39c7ce37 100644 --- a/appview/pages/pages.go +++ b/appview/pages/pages.go @@ -604,13 +604,14 @@ func (p *Pages) ForkRepo(w io.Writer, params ForkRepoParams) error { } type ProfileCard struct { - UserDid string - HasProfile bool - FollowStatus models.FollowStatus - Punchcard *models.Punchcard - Profile *models.Profile - Stats ProfileStats - Active string + UserDid string + HasProfile bool + FollowStatus models.FollowStatus + VouchRelationship *models.VouchRelationship + Punchcard *models.Punchcard + Profile *models.Profile + Stats ProfileStats + Active string } type ProfileStats struct { diff --git a/appview/pages/templates/user/fragments/profileCard.html b/appview/pages/templates/user/fragments/profileCard.html index c9b5e147..a19314df 100644 --- a/appview/pages/templates/user/fragments/profileCard.html +++ b/appview/pages/templates/user/fragments/profileCard.html @@ -87,7 +87,7 @@ {{ end }} -