Monorepo for Tangled tangled.org
9

Configure Feed

Select the types of activity you want to include in your feed.

1package models 2 3import ( 4 "errors" 5 "fmt" 6 "time" 7 "unicode/utf8" 8 9 "github.com/bluesky-social/indigo/atproto/syntax" 10 lexutil "github.com/bluesky-social/indigo/lex/util" 11 "tangled.org/core/api/tangled" 12) 13 14type String struct { 15 Did syntax.DID 16 Rkey syntax.RecordKey 17 Cid *syntax.CID 18 19 // String_File will remain, and we will still use it. 20 // We just use `FileContent` when fetching the file. 21 22 // after that, change lexicon, start migrating to blobs 23 // when string is migrated, clear FileName and FileContent. 24 // when they are cleared, fetch the blob on page load. 25 26 Title *string 27 Description *string 28 Files []String_File 29 Created time.Time 30 Edited *time.Time 31 32 // legacy string data 33 FileName string 34 FileContent string 35 36 // optionally, populate this when querying for reverse mappings 37 Stats *StringStats 38} 39 40// String_File is [tangled.String_File] with optional prefetched & decompressed text blob content 41type String_File struct { 42 Name string 43 Content lexutil.LexBlob 44 Gzip *String_GzipInfo 45} 46type String_GzipInfo struct { 47 tangled.String_File_Gzip 48 // Optional uncompressed content. 49 // Populated when the content is first requested. 50 Content string 51} 52 53func (s *String) AtUri() syntax.ATURI { 54 return syntax.ATURI(fmt.Sprintf("at://%s/%s/%s", s.Did, tangled.StringNSID, s.Rkey)) 55} 56 57func (s *String) AsRecord() *tangled.String { 58 var files []*tangled.String_File 59 for _, f := range s.Files { 60 var gzip *tangled.String_File_Gzip 61 if f.Gzip != nil { 62 gzip = &tangled.String_File_Gzip{ 63 RealSize: f.Gzip.RealSize, 64 RealMime: f.Gzip.RealMime, 65 } 66 } 67 files = append(files, &tangled.String_File{ 68 Name: f.Name, 69 Content: &f.Content, 70 Gzip: gzip, 71 }) 72 } 73 return &tangled.String{ 74 Title: s.Title, 75 Description: s.Description, 76 Files: files, 77 CreatedAt: s.Created.Format(time.RFC3339), 78 } 79} 80 81func (s *String) Validate() error { 82 var err error 83 if s.FileName == "" && len(s.Files) == 0 { 84 err = errors.Join(err, fmt.Errorf("string should have more than one files")) 85 } 86 // legacy record check 87 if utf8.RuneCountInString(s.FileName) > 140 { 88 err = errors.Join(err, fmt.Errorf("filename too long")) 89 } 90 for i, file := range s.Files { 91 if utf8.RuneCountInString(file.Name) > 140 { 92 err = errors.Join(err, fmt.Errorf("filename too long at files[%d]", i)) 93 } 94 } 95 if s.Title != nil { 96 if utf8.RuneCountInString(*s.Title) > 140 { 97 err = errors.Join(err, fmt.Errorf("title too long")) 98 } 99 } 100 if s.Description != nil { 101 if utf8.RuneCountInString(*s.Description) > 280 { 102 err = errors.Join(err, fmt.Errorf("description too long")) 103 } 104 } 105 return err 106} 107 108func (s String) RenderTitle() string { 109 if s.Title != nil { 110 return *s.Title 111 } 112 if len(s.Files) > 0 { 113 return s.Files[0].Name 114 } 115 return s.FileName 116} 117 118// FileByName returns first item in files with given filename 119func (s *String) FileByName(name string) (String_File, bool) { 120 for _, file := range s.Files { 121 if file.Name == name { 122 return file, true 123 } 124 } 125 return String_File{}, false 126} 127 128func (s String) IsLegacySingleFile() bool { 129 return len(s.Files) == 0 130} 131 132// StringFromRecord creates [String] from [tangled.String]. 133// NOTE: This won't prefetch blobs 134func StringFromRecord(did syntax.DID, rkey syntax.RecordKey, cid syntax.CID, record tangled.String) (String, error) { 135 created, err := time.Parse(time.RFC3339, record.CreatedAt) 136 if err != nil { 137 return String{}, fmt.Errorf("invalid createdAt: %w", err) 138 } 139 var files []String_File 140 for _, f := range record.Files { 141 var gzip *String_GzipInfo 142 if f.Gzip != nil { 143 gzip = &String_GzipInfo{String_File_Gzip: *f.Gzip} 144 } 145 files = append(files, String_File{ 146 Name: f.Name, 147 Content: *f.Content, 148 Gzip: gzip, 149 }) 150 } 151 return String{ 152 Did: did, 153 Rkey: rkey, 154 Cid: &cid, 155 Title: record.Title, 156 Description: record.Description, 157 Files: files, 158 Created: created, 159 FileName: stringPtr(record.Filename), 160 FileContent: stringPtr(record.Contents), 161 }, nil 162} 163 164type StringStats struct { 165 StarCount int 166 // CommentCount int 167} 168 169type StringFileStats struct { 170 LineCount int 171 ByteCount int 172} 173 174func stringPtr(s *string) string { 175 if s == nil { 176 return "" 177 } 178 return *s 179}