A terminal client for Tangled, the git forge built on AT Protocol.
Something went wrong. Try again.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138package app
import ( "context" "encoding/json" "fmt" "time"
"github.com/alyraffauf/tg/atproto" "github.com/alyraffauf/tg/internal/tangledlex" "github.com/bluesky-social/indigo/atproto/syntax")
// CreateStringInput configures string creation.type CreateStringInput struct { Filename string Description string Contents string}
// CreateString writes a new sh.tangled.string record.func (s *Service) CreateString(ctx context.Context, in CreateStringInput) (*CreatedRecordResult, error) { atClient, did, err := s.authenticatedPDS(ctx) if err != nil { return nil, err } rkey := string(syntax.NewTIDNow(0)) uri, _, err := atClient.PutRecord(ctx, atproto.PutRecordInput{ Repo: did, Collection: stringCollection, Rkey: rkey, Record: tangledlex.String{ LexiconTypeID: stringCollection, Filename: in.Filename, Description: in.Description, Contents: in.Contents, CreatedAt: time.Now().UTC().Format(time.RFC3339), }, }) if err != nil { return nil, fmt.Errorf("create string: %w", err) } return &CreatedRecordResult{Rkey: rkey, URI: uri}, nil}
// ListStrings lists every string owned by handle.func (s *Service) ListStrings(ctx context.Context, handle string) ([]StringItem, error) { atClient, did, err := s.publicPDS(ctx, handle) if err != nil { return nil, err } records, err := atClient.ListAllRecords(ctx, did, stringCollection, atproto.ListRecordsOpts{Limit: defaultListLimit}) if err != nil { return nil, fmt.Errorf("list strings for %q: %w", handle, err) } return buildStringItems(records), nil}
// ViewString fetches a single string by rkey from handle's account.func (s *Service) ViewString(ctx context.Context, handle, rkey string) (*StringViewResult, error) { atClient, did, err := s.publicPDS(ctx, handle) if err != nil { return nil, err } found, err := atClient.GetRecord(ctx, did, stringCollection, rkey) if err != nil { return nil, fmt.Errorf("get string %q for %q: %w", rkey, handle, err) } record, err := decodeStringRecord(found.Value) if err != nil { return nil, fmt.Errorf("decode string %q: %w", rkey, err) } return &StringViewResult{ Rkey: rkey, URI: found.URI, Filename: record.Filename, Author: Author{DID: did, Handle: handle}, Description: record.Description, Contents: record.Contents, CreatedAt: record.CreatedAt, }, nil}
// DeleteString removes a string record from the authenticated user's account.func (s *Service) DeleteString(ctx context.Context, rkey string) (*DeletedRecordResult, error) { atClient, did, err := s.authenticatedPDS(ctx) if err != nil { return nil, err } if err := atClient.DeleteRecord(ctx, atproto.DeleteRecordInput{ Repo: did, Collection: stringCollection, Rkey: rkey, }); err != nil { return nil, fmt.Errorf("delete string: %w", err) } return &DeletedRecordResult{Rkey: rkey}, nil}
func buildStringItems(records []atproto.RecordItem) []StringItem { items := make([]StringItem, 0, len(records)) for _, rec := range records { var str tangledlex.String data, err := json.Marshal(rec.Value) if err != nil { continue } if err := json.Unmarshal(data, &str); err != nil { continue } // Records without a filename are not strings; skip them rather // than rendering a blank row. if str.Filename == "" { continue } items = append(items, StringItem{ Rkey: extractRKey(rec.URI), URI: rec.URI, Filename: str.Filename, Description: str.Description, CreatedAt: str.CreatedAt, }) } return items}
func decodeStringRecord(value any) (tangledlex.String, error) { data, err := json.Marshal(value) if err != nil { return tangledlex.String{}, fmt.Errorf("encode record: %w", err) } var record tangledlex.String if err := json.Unmarshal(data, &record); err != nil { return tangledlex.String{}, fmt.Errorf("decode record: %w", err) } return record, nil}