Monorepo for Tangled tangled.org
3

Configure Feed

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

1package models 2 3import ( 4 "fmt" 5 "strings" 6 "time" 7 8 "github.com/bluesky-social/indigo/atproto/syntax" 9 securejoin "github.com/cyphar/filepath-securejoin" 10 enry "github.com/go-enry/go-enry/v2" 11 "tangled.org/core/api/tangled" 12) 13 14type Repo struct { 15 Id int64 16 Did string 17 Name string 18 Knot string 19 Rkey string 20 Created time.Time 21 Description string 22 Website string 23 Topics []string 24 Spindle string 25 Labels []string 26 RepoDid string 27 28 // optionally, populate this when querying for reverse mappings 29 RepoStats *RepoStats 30 31 // optional 32 Source string 33} 34 35func (r *Repo) AsRecord() tangled.Repo { 36 var source, spindle, description, website *string 37 38 if r.Source != "" { 39 source = &r.Source 40 } 41 42 if r.Spindle != "" { 43 spindle = &r.Spindle 44 } 45 46 if r.Description != "" { 47 description = &r.Description 48 } 49 50 if r.Website != "" { 51 website = &r.Website 52 } 53 54 var repoDid *string 55 if r.RepoDid != "" { 56 repoDid = &r.RepoDid 57 } 58 59 return tangled.Repo{ 60 Knot: r.Knot, 61 Name: r.cosmeticName(), 62 Description: description, 63 Website: website, 64 Topics: r.Topics, 65 CreatedAt: r.Created.Format(time.RFC3339), 66 Source: source, 67 Spindle: spindle, 68 Labels: r.Labels, 69 RepoDid: repoDid, 70 } 71} 72 73func (r *Repo) cosmeticName() *string { 74 if r.Name == "" || r.Name == r.Rkey { 75 return nil 76 } 77 return &r.Name 78} 79 80func (r Repo) RepoAt() syntax.ATURI { 81 return syntax.ATURI(fmt.Sprintf("at://%s/%s/%s", r.Did, tangled.RepoNSID, r.Rkey)) 82} 83 84func (r Repo) Slug() string { 85 if r.Name != "" { 86 return r.Name 87 } 88 return r.Rkey 89} 90 91func (r Repo) RepoIdentifier() string { 92 if r.RepoDid != "" { 93 return r.RepoDid 94 } 95 p, _ := securejoin.SecureJoin(r.Did, r.Rkey) 96 return p 97} 98 99func (r Repo) PinIdentifier() string { 100 if r.RepoDid != "" { 101 return r.RepoDid 102 } 103 return string(r.RepoAt()) 104} 105 106func (r Repo) TopicStr() string { 107 return strings.Join(r.Topics, " ") 108} 109 110type RepoStats struct { 111 Language string 112 StarCount int 113 IssueCount IssueCount 114 PullCount PullCount 115 ForkCount int 116} 117 118// returns the first file extension for the language ("ts" for typescript) as 119// an uppercase string 120func (s *RepoStats) LangShortName() string { 121 if s == nil || s.Language == "" { 122 return "" 123 } 124 exts := enry.GetLanguageExtensions(s.Language) 125 if len(exts) > 0 { 126 // extensions include the leading dot, e.g. ".ts" -> "TS" 127 return strings.ToUpper(strings.TrimPrefix(exts[0], ".")) 128 } 129 return s.Language 130} 131 132type IssueCount struct { 133 Open int 134 Closed int 135} 136 137type PullCount struct { 138 Open int 139 Merged int 140 Closed int 141 Deleted int 142} 143 144type RepoLabel struct { 145 Id int64 146 RepoDid syntax.DID 147 LabelAt syntax.ATURI 148} 149 150var reservedRepoNames = map[string]struct{}{ 151 "self": {}, 152} 153 154func ValidateRepoName(name string) error { 155 if len(name) == 0 { 156 return fmt.Errorf("Repository name cannot be empty") 157 } 158 if len(name) > 100 { 159 return fmt.Errorf("Repository name must be 100 characters or fewer") 160 } 161 162 // check for path traversal attempts 163 if strings.Contains(name, "/") || strings.Contains(name, "\\") { 164 return fmt.Errorf("Repository name contains invalid path characters") 165 } 166 167 // check for sequences that could be used for traversal when normalized 168 if strings.HasPrefix(name, ".") || strings.HasSuffix(name, ".") { 169 return fmt.Errorf("Repository name contains invalid path sequence") 170 } 171 172 // then continue with character validation 173 for _, char := range name { 174 if !((char >= 'a' && char <= 'z') || 175 (char >= 'A' && char <= 'Z') || 176 (char >= '0' && char <= '9') || 177 char == '-' || char == '_' || char == '.') { 178 return fmt.Errorf("Repository name can only contain alphanumeric characters, periods, hyphens, and underscores") 179 } 180 } 181 182 // additional check to prevent multiple sequential dots 183 if strings.Contains(name, "..") { 184 return fmt.Errorf("Repository name cannot contain sequential dots") 185 } 186 187 if _, reserved := reservedRepoNames[strings.ToLower(name)]; reserved { 188 return fmt.Errorf("Repository name %q is reserved", name) 189 } 190 191 // if all checks pass 192 return nil 193} 194 195func StripGitExt(name string) string { 196 return strings.TrimSuffix(name, ".git") 197} 198 199type RepoGroup struct { 200 Repo *Repo 201 Issues []Issue 202} 203 204type BlobContentType int 205 206const ( 207 BlobContentTypeCode BlobContentType = iota 208 BlobContentTypeMarkup 209 BlobContentTypeImage 210 BlobContentTypeSvg 211 BlobContentTypeVideo 212 BlobContentTypeSubmodule 213) 214 215func (ty BlobContentType) IsCode() bool { return ty == BlobContentTypeCode } 216func (ty BlobContentType) IsMarkup() bool { return ty == BlobContentTypeMarkup } 217func (ty BlobContentType) IsImage() bool { return ty == BlobContentTypeImage } 218func (ty BlobContentType) IsSvg() bool { return ty == BlobContentTypeSvg } 219func (ty BlobContentType) IsVideo() bool { return ty == BlobContentTypeVideo } 220func (ty BlobContentType) IsSubmodule() bool { return ty == BlobContentTypeSubmodule } 221 222type BlobView struct { 223 HasTextView bool // can show as code/text 224 HasRenderedView bool // can show rendered (markup/image/video/submodule) 225 HasRawView bool // can download raw (everything except submodule) 226 FileTooLarge bool // file too large (ignored for image files) 227 228 // current display mode 229 ShowingRendered bool // currently in rendered mode 230 231 // content type flags 232 ContentType BlobContentType 233 234 // Content data 235 Contents string 236 ContentSrc string // URL for media files 237 Lines int 238 SizeHint uint64 239} 240 241// if both views are available, then show a toggle between them 242func (b BlobView) ShowToggle() bool { 243 return b.HasTextView && b.HasRenderedView 244} 245 246func (b BlobView) IsUnsupported() bool { 247 // no view available, only raw 248 return !(b.HasRenderedView || b.HasTextView) 249} 250 251func (b BlobView) ShowingText() bool { 252 return !b.ShowingRendered 253} 254 255func (b BlobView) ShowCopy() bool { 256 return b.ContentType.IsCode() || b.ContentType.IsMarkup() || b.ContentType.IsSvg() || b.ContentType.IsImage() 257}