diff --git a/api/tangled/cbor_gen.go b/api/tangled/cbor_gen.go --- a/api/tangled/cbor_gen.go +++ b/api/tangled/cbor_gen.go @@ -5806,7 +5806,7 @@ return err } cw := cbg.NewCborWriter(w) - fieldCount := 8 + fieldCount := 10 if t.Description == nil { fieldCount-- @@ -5821,6 +5821,14 @@ fieldCount-- } if t.Spindle == nil { + fieldCount-- + } + + if t.Topics == nil { + fieldCount-- + } + + if t.Website == nil { fieldCount-- } @@ -5961,6 +5969,42 @@ } } } + // t.Topics ([]string) (slice) + if t.Topics != nil { + + if len("topics") > 1000000 { + return xerrors.Errorf("Value in field \"topics\" was too long") + } + + if err := cw.WriteMajorTypeHeader(cbg.MajTextString, uint64(len("topics"))); err != nil { + return err + } + if _, err := cw.WriteString(string("topics")); err != nil { + return err + } + + if len(t.Topics) > 8192 { + return xerrors.Errorf("Slice value in field t.Topics was too long") + } + + if err := cw.WriteMajorTypeHeader(cbg.MajArray, uint64(len(t.Topics))); err != nil { + return err + } + for _, v := range t.Topics { + if len(v) > 1000000 { + return xerrors.Errorf("Value in field v was too long") + } + + if err := cw.WriteMajorTypeHeader(cbg.MajTextString, uint64(len(v))); err != nil { + return err + } + if _, err := cw.WriteString(string(v)); err != nil { + return err + } + + } + } + // t.Spindle (string) (string) if t.Spindle != nil { @@ -5993,6 +6037,38 @@ } } } + // t.Website (string) (string) + if t.Website != nil { + + if len("website") > 1000000 { + return xerrors.Errorf("Value in field \"website\" was too long") + } + + if err := cw.WriteMajorTypeHeader(cbg.MajTextString, uint64(len("website"))); err != nil { + return err + } + if _, err := cw.WriteString(string("website")); err != nil { + return err + } + + if t.Website == nil { + if _, err := cw.Write(cbg.CborNull); err != nil { + return err + } + } else { + if len(*t.Website) > 1000000 { + return xerrors.Errorf("Value in field t.Website was too long") + } + + if err := cw.WriteMajorTypeHeader(cbg.MajTextString, uint64(len(*t.Website))); err != nil { + return err + } + if _, err := cw.WriteString(string(*t.Website)); err != nil { + return err + } + } + } + // t.CreatedAt (string) (string) if len("createdAt") > 1000000 { return xerrors.Errorf("Value in field \"createdAt\" was too long") @@ -6185,6 +6261,46 @@ t.Source = (*string)(&sval) } } + // t.Topics ([]string) (slice) + case "topics": + + maj, extra, err = cr.ReadHeader() + if err != nil { + return err + } + + if extra > 8192 { + return fmt.Errorf("t.Topics: array too large (%d)", extra) + } + + if maj != cbg.MajArray { + return fmt.Errorf("expected cbor array") + } + + if extra > 0 { + t.Topics = make([]string, extra) + } + + for i := 0; i < int(extra); i++ { + { + var maj byte + var extra uint64 + var err error + _ = maj + _ = extra + _ = err + + { + sval, err := cbg.ReadStringWithMax(cr, 1000000) + if err != nil { + return err + } + + t.Topics[i] = string(sval) + } + + } + } // t.Spindle (string) (string) case "spindle": @@ -6204,6 +6320,27 @@ return err } t.Spindle = (*string)(&sval) + } + } + // t.Website (string) (string) + case "website": + + { + b, err := cr.ReadByte() + if err != nil { + return err + } + if b != cbg.CborNull[0] { + if err := cr.UnreadByte(); err != nil { + return err + } + + sval, err := cbg.ReadStringWithMax(cr, 1000000) + if err != nil { + return err + } + + t.Website = (*string)(&sval) } } // t.CreatedAt (string) (string) diff --git a/api/tangled/tangledrepo.go b/api/tangled/tangledrepo.go --- a/api/tangled/tangledrepo.go +++ b/api/tangled/tangledrepo.go @@ -30,4 +30,8 @@ // source: source of the repo Source *string `json:"source,omitempty" cborgen:"source,omitempty"` // spindle: CI runner to send jobs to and receive results from Spindle *string `json:"spindle,omitempty" cborgen:"spindle,omitempty"` + // topics: Topics related to the repo + Topics []string `json:"topics,omitempty" cborgen:"topics,omitempty"` + // website: Any URI related to the repo + Website *string `json:"website,omitempty" cborgen:"website,omitempty"` } diff --git a/appview/db/db.go b/appview/db/db.go --- a/appview/db/db.go +++ b/appview/db/db.go @@ -1113,6 +1113,14 @@ `) return err }) + runMigration(conn, logger, "add-meta-column-repos", func(tx *sql.Tx) error { + _, err := tx.Exec(` + alter table repos add column website text; + alter table repos add column topics text; + `) + return err + }) + return &DB{ db, logger, diff --git a/appview/db/notifications.go b/appview/db/notifications.go --- a/appview/db/notifications.go +++ b/appview/db/notifications.go @@ -134,7 +134,7 @@ query := fmt.Sprintf(` select n.id, n.recipient_did, n.actor_did, n.type, n.entity_type, n.entity_id, n.read, n.created, n.repo_id, n.issue_id, n.pull_id, - r.id as r_id, r.did as r_did, r.name as r_name, r.description as r_description, + r.id as r_id, r.did as r_did, r.name as r_name, r.description as r_description, r.website as r_website, r.topics as r_topics, i.id as i_id, i.did as i_did, i.issue_id as i_issue_id, i.title as i_title, i.open as i_open, p.id as p_id, p.owner_did as p_owner_did, p.pull_id as p_pull_id, p.title as p_title, p.state as p_state from notifications n @@ -163,7 +163,7 @@ var repo models.Repo var issue models.Issue var pull models.Pull var rId, iId, pId sql.NullInt64 - var rDid, rName, rDescription sql.NullString + var rDid, rName, rDescription, rWebsite, rTopicStr sql.NullString var iDid sql.NullString var iIssueId sql.NullInt64 var iTitle sql.NullString @@ -176,7 +176,7 @@ err := rows.Scan( &n.ID, &n.RecipientDid, &n.ActorDid, &typeStr, &n.EntityType, &n.EntityId, &n.Read, &createdStr, &n.RepoId, &n.IssueId, &n.PullId, - &rId, &rDid, &rName, &rDescription, + &rId, &rDid, &rName, &rDescription, &rWebsite, &rTopicStr, &iId, &iDid, &iIssueId, &iTitle, &iOpen, &pId, &pOwnerDid, &pPullId, &pTitle, &pState, ) @@ -203,6 +203,12 @@ repo.Name = rName.String } if rDescription.Valid { repo.Description = rDescription.String + } + if rWebsite.Valid { + repo.Website = rWebsite.String + } + if rTopicStr.Valid { + repo.Topics = strings.Fields(rTopicStr.String) } nwe.Repo = &repo } diff --git a/appview/db/repos.go b/appview/db/repos.go --- a/appview/db/repos.go +++ b/appview/db/repos.go @@ -70,6 +70,8 @@ knot, rkey, created, description, + website, + topics, source, spindle from @@ -89,7 +91,7 @@ for rows.Next() { var repo models.Repo var createdAt string - var description, source, spindle sql.NullString + var description, website, topicStr, source, spindle sql.NullString err := rows.Scan( &repo.Id, @@ -99,6 +101,8 @@ &repo.Knot, &repo.Rkey, &createdAt, &description, + &website, + &topicStr, &source, &spindle, ) @@ -111,6 +115,12 @@ repo.Created = t } if description.Valid { repo.Description = description.String + } + if website.Valid { + repo.Website = website.String + } + if topicStr.Valid { + repo.Topics = strings.Fields(topicStr.String) } if source.Valid { repo.Source = source.String @@ -356,11 +366,13 @@ func GetRepoByAtUri(e Execer, atUri string) (*models.Repo, error) { var repo models.Repo var nullableDescription sql.NullString + var nullableWebsite sql.NullString + var nullableTopicStr sql.NullString - row := e.QueryRow(`select id, did, name, knot, created, rkey, description from repos where at_uri = ?`, atUri) + row := e.QueryRow(`select id, did, name, knot, created, rkey, description, website, topics from repos where at_uri = ?`, atUri) var createdAt string - if err := row.Scan(&repo.Id, &repo.Did, &repo.Name, &repo.Knot, &createdAt, &repo.Rkey, &nullableDescription); err != nil { + if err := row.Scan(&repo.Id, &repo.Did, &repo.Name, &repo.Knot, &createdAt, &repo.Rkey, &nullableDescription, &nullableWebsite, &nullableTopicStr); err != nil { return nil, err } createdAtTime, _ := time.Parse(time.RFC3339, createdAt) @@ -368,19 +380,34 @@ repo.Created = createdAtTime if nullableDescription.Valid { repo.Description = nullableDescription.String - } else { - repo.Description = "" + } + if nullableWebsite.Valid { + repo.Website = nullableWebsite.String + } + if nullableTopicStr.Valid { + repo.Topics = strings.Fields(nullableTopicStr.String) } return &repo, nil +} + +func PutRepo(tx *sql.Tx, repo models.Repo) error { + _, err := tx.Exec( + `update repos + set knot = ?, description = ?, website = ?, topics = ? + where did = ? and rkey = ? + `, + repo.Knot, repo.Description, repo.Website, repo.TopicStr(), repo.Did, repo.Rkey, + ) + return err } func AddRepo(tx *sql.Tx, repo *models.Repo) error { _, err := tx.Exec( `insert into repos - (did, name, knot, rkey, at_uri, description, source) - values (?, ?, ?, ?, ?, ?, ?)`, - repo.Did, repo.Name, repo.Knot, repo.Rkey, repo.RepoAt().String(), repo.Description, repo.Source, + (did, name, knot, rkey, at_uri, description, website, topics source) + values (?, ?, ?, ?, ?, ?, ?, ?, ?)`, + repo.Did, repo.Name, repo.Knot, repo.Rkey, repo.RepoAt().String(), repo.Description, repo.Website, repo.TopicStr(), repo.Source, ) if err != nil { return fmt.Errorf("failed to insert repo: %w", err) @@ -416,7 +443,7 @@ func GetForksByDid(e Execer, did string) ([]models.Repo, error) { var repos []models.Repo rows, err := e.Query( - `select distinct r.id, r.did, r.name, r.knot, r.rkey, r.description, r.created, r.source + `select distinct r.id, r.did, r.name, r.knot, r.rkey, r.description, r.website, r.created, r.source from repos r left join collaborators c on r.at_uri = c.repo_at where (r.did = ? or c.subject_did = ?) @@ -434,9 +461,10 @@ for rows.Next() { var repo models.Repo var createdAt string var nullableDescription sql.NullString + var nullableWebsite sql.NullString var nullableSource sql.NullString - err := rows.Scan(&repo.Id, &repo.Did, &repo.Name, &repo.Knot, &repo.Rkey, &nullableDescription, &createdAt, &nullableSource) + err := rows.Scan(&repo.Id, &repo.Did, &repo.Name, &repo.Knot, &repo.Rkey, &nullableDescription, &nullableWebsite, &createdAt, &nullableSource) if err != nil { return nil, err } @@ -470,22 +498,32 @@ func GetForkByDid(e Execer, did string, name string) (*models.Repo, error) { var repo models.Repo var createdAt string var nullableDescription sql.NullString + var nullableWebsite sql.NullString + var nullableTopicStr sql.NullString var nullableSource sql.NullString row := e.QueryRow( - `select id, did, name, knot, rkey, description, created, source + `select id, did, name, knot, rkey, description, website, topics, created, source from repos where did = ? and name = ? and source is not null and source != ''`, did, name, ) - err := row.Scan(&repo.Id, &repo.Did, &repo.Name, &repo.Knot, &repo.Rkey, &nullableDescription, &createdAt, &nullableSource) + err := row.Scan(&repo.Id, &repo.Did, &repo.Name, &repo.Knot, &repo.Rkey, &nullableDescription, &nullableWebsite, &nullableTopicStr, &createdAt, &nullableSource) if err != nil { return nil, err } if nullableDescription.Valid { repo.Description = nullableDescription.String + } + + if nullableWebsite.Valid { + repo.Website = nullableWebsite.String + } + + if nullableTopicStr.Valid { + repo.Topics = strings.Fields(nullableTopicStr.String) } if nullableSource.Valid { diff --git a/appview/models/repo.go b/appview/models/repo.go --- a/appview/models/repo.go +++ b/appview/models/repo.go @@ -2,6 +2,7 @@ package models import ( "fmt" + "strings" "time" "github.com/bluesky-social/indigo/atproto/syntax" @@ -17,6 +18,8 @@ Knot string Rkey string Created time.Time Description string + Website string + Topics []string Spindle string Labels []string @@ -28,7 +31,7 @@ Source string } func (r *Repo) AsRecord() tangled.Repo { - var source, spindle, description *string + var source, spindle, description, website *string if r.Source != "" { source = &r.Source @@ -42,10 +45,16 @@ if r.Description != "" { description = &r.Description } + if r.Website != "" { + website = &r.Website + } + return tangled.Repo{ Knot: r.Knot, Name: r.Name, Description: description, + Website: website, + Topics: r.Topics, CreatedAt: r.Created.Format(time.RFC3339), Source: source, Spindle: spindle, @@ -60,6 +69,10 @@ func (r Repo) DidSlashRepo() string { p, _ := securejoin.SecureJoin(r.Did, r.Name) return p +} + +func (r Repo) TopicStr() string { + return strings.Join(r.Topics, " ") } type RepoStats struct { diff --git a/appview/pages/funcmap.go b/appview/pages/funcmap.go --- a/appview/pages/funcmap.go +++ b/appview/pages/funcmap.go @@ -246,6 +246,11 @@ htmlString := p.rctx.RenderMarkdown(text) sanitized := p.rctx.SanitizeDescription(htmlString) return template.HTML(sanitized) }, + "trimUriScheme": func(text string) string { + text = strings.TrimPrefix(text, "https://") + text = strings.TrimPrefix(text, "http://") + return text + }, "isNil": func(t any) bool { // returns false for other "zero" values return t == nil diff --git a/appview/pages/pages.go b/appview/pages/pages.go --- a/appview/pages/pages.go +++ b/appview/pages/pages.go @@ -640,18 +640,6 @@ func (p *Pages) RepoStarFragment(w io.Writer, params RepoStarFragmentParams) error { return p.executePlain("repo/fragments/repoStar", w, params) } -type RepoDescriptionParams struct { - RepoInfo repoinfo.RepoInfo -} - -func (p *Pages) EditRepoDescriptionFragment(w io.Writer, params RepoDescriptionParams) error { - return p.executePlain("repo/fragments/editRepoDescription", w, params) -} - -func (p *Pages) RepoDescriptionFragment(w io.Writer, params RepoDescriptionParams) error { - return p.executePlain("repo/fragments/repoDescription", w, params) -} - type RepoIndexParams struct { LoggedInUser *oauth.User RepoInfo repoinfo.RepoInfo diff --git a/appview/pages/repoinfo/repoinfo.go b/appview/pages/repoinfo/repoinfo.go --- a/appview/pages/repoinfo/repoinfo.go +++ b/appview/pages/repoinfo/repoinfo.go @@ -54,6 +54,8 @@ Rkey string OwnerDid string OwnerHandle string Description string + Website string + Topics []string Knot string Spindle string RepoAt syntax.ATURI diff --git a/appview/pages/templates/layouts/repobase.html b/appview/pages/templates/layouts/repobase.html --- a/appview/pages/templates/layouts/repobase.html +++ b/appview/pages/templates/layouts/repobase.html @@ -17,6 +17,9 @@