Monorepo for Tangled
Something went wrong. Try again.
Go
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243package models
import ( "fmt" "strings" "time"
"github.com/bluesky-social/indigo/atproto/syntax" securejoin "github.com/cyphar/filepath-securejoin" enry "github.com/go-enry/go-enry/v2" "tangled.org/core/api/tangled")
type Repo struct { Id int64 Did string Name string Knot string Rkey string Created time.Time Description string Website string Topics []string Spindle string Labels []string RepoDid string
// optionally, populate this when querying for reverse mappings RepoStats *RepoStats
// optional Source string}
func (r *Repo) AsRecord() tangled.Repo { var source, spindle, description, website *string
if r.Source != "" { source = &r.Source }
if r.Spindle != "" { spindle = &r.Spindle }
if r.Description != "" { description = &r.Description }
if r.Website != "" { website = &r.Website }
var repoDid *string if r.RepoDid != "" { repoDid = &r.RepoDid }
return tangled.Repo{ Knot: r.Knot, Name: r.cosmeticName(), Description: description, Website: website, Topics: r.Topics, CreatedAt: r.Created.Format(time.RFC3339), Source: source, Spindle: spindle, Labels: r.Labels, RepoDid: repoDid, }}
func (r *Repo) cosmeticName() *string { if r.Name == "" || r.Name == r.Rkey { return nil } return &r.Name}
func (r Repo) RepoAt() syntax.ATURI { return syntax.ATURI(fmt.Sprintf("at://%s/%s/%s", r.Did, tangled.RepoNSID, r.Rkey))}
func (r Repo) Slug() string { if r.Name != "" { return r.Name } return r.Rkey}
func (r Repo) RepoIdentifier() string { if r.RepoDid != "" { return r.RepoDid } p, _ := securejoin.SecureJoin(r.Did, r.Rkey) return p}
func (r Repo) PinIdentifier() string { if r.RepoDid != "" { return r.RepoDid } return string(r.RepoAt())}
func (r Repo) TopicStr() string { return strings.Join(r.Topics, " ")}
type RepoStats struct { Language string StarCount int IssueCount IssueCount PullCount PullCount ForkCount int}
// returns the first file extension for the language ("ts" for typescript) as// an uppercase stringfunc (s *RepoStats) LangShortName() string { if s == nil || s.Language == "" { return "" } exts := enry.GetLanguageExtensions(s.Language) if len(exts) > 0 { // extensions include the leading dot, e.g. ".ts" -> "TS" return strings.ToUpper(strings.TrimPrefix(exts[0], ".")) } return s.Language}
type IssueCount struct { Open int Closed int}
type PullCount struct { Open int Merged int Closed int Deleted int}
type RepoLabel struct { Id int64 RepoDid syntax.DID LabelAt syntax.ATURI}
var reservedRepoNames = map[string]struct{}{ "self": {},}
func ValidateRepoName(name string) error { if len(name) == 0 { return fmt.Errorf("Repository name cannot be empty") } if len(name) > 100 { return fmt.Errorf("Repository name must be 100 characters or fewer") }
// check for path traversal attempts if strings.Contains(name, "/") || strings.Contains(name, "\\") { return fmt.Errorf("Repository name contains invalid path characters") }
// check for sequences that could be used for traversal when normalized if strings.HasPrefix(name, ".") || strings.HasSuffix(name, ".") { return fmt.Errorf("Repository name contains invalid path sequence") }
// then continue with character validation for _, char := range name { if !((char >= 'a' && char <= 'z') || (char >= 'A' && char <= 'Z') || (char >= '0' && char <= '9') || char == '-' || char == '_' || char == '.') { return fmt.Errorf("Repository name can only contain alphanumeric characters, periods, hyphens, and underscores") } }
// additional check to prevent multiple sequential dots if strings.Contains(name, "..") { return fmt.Errorf("Repository name cannot contain sequential dots") }
if _, reserved := reservedRepoNames[strings.ToLower(name)]; reserved { return fmt.Errorf("Repository name %q is reserved", name) }
// if all checks pass return nil}
func StripGitExt(name string) string { return strings.TrimSuffix(name, ".git")}
type RepoGroup struct { Repo *Repo Issues []Issue}
type BlobContentType int
const ( BlobContentTypeCode BlobContentType = iota BlobContentTypeMarkup BlobContentTypeImage BlobContentTypeSvg BlobContentTypeVideo BlobContentTypeSubmodule BlobContentTypeOther)
func (ty BlobContentType) IsCode() bool { return ty == BlobContentTypeCode }func (ty BlobContentType) IsMarkup() bool { return ty == BlobContentTypeMarkup }func (ty BlobContentType) IsImage() bool { return ty == BlobContentTypeImage }func (ty BlobContentType) IsSvg() bool { return ty == BlobContentTypeSvg }func (ty BlobContentType) IsVideo() bool { return ty == BlobContentTypeVideo }func (ty BlobContentType) IsSubmodule() bool { return ty == BlobContentTypeSubmodule }func (ty BlobContentType) HasTextView() bool { return ty == BlobContentTypeCode || ty == BlobContentTypeMarkup || ty == BlobContentTypeSvg}func (ty BlobContentType) HasRenderedView() bool { return ty != BlobContentTypeCode && ty != BlobContentTypeOther}func (ty BlobContentType) HasRawView() bool { return ty != BlobContentTypeSubmodule}
type BlobView struct { // content type flags ContentType BlobContentType
// Content data ContentSrc string // URL to raw content Contents string // textual content FileTooLarge bool // textual content is too large Lines int // line count of textual content SizeHint uint64}