This repository has no description
0

Configure Feed

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

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