···13)
1415func init() {
16- // util.RegisterType("sh.tangled.repo.artifact", &RepoArtifact{})
17} //
18// RECORDTYPE: RepoArtifact
19type RepoArtifact struct {
···25 // name: name of the artifact
26 Name string `json:"name" cborgen:"name"`
27 // repo: repo that this artifact is being uploaded to
28- Repo string `json:"repo" cborgen:"repo"`
29 RepoDid *string `json:"repoDid,omitempty" cborgen:"repoDid,omitempty"`
30 // tag: hash of the tag object that this artifact is attached to (only annotated tags are supported)
31 Tag util.LexBytes `json:"tag,omitempty" cborgen:"tag,omitempty"`
···13)
1415func init() {
16+ util.RegisterType("sh.tangled.repo.artifact", &RepoArtifact{})
17} //
18// RECORDTYPE: RepoArtifact
19type RepoArtifact struct {
···25 // name: name of the artifact
26 Name string `json:"name" cborgen:"name"`
27 // repo: repo that this artifact is being uploaded to
28+ Repo *string `json:"repo,omitempty" cborgen:"repo,omitempty"`
29 RepoDid *string `json:"repoDid,omitempty" cborgen:"repoDid,omitempty"`
30 // tag: hash of the tag object that this artifact is attached to (only annotated tags are supported)
31 Tag util.LexBytes `json:"tag,omitempty" cborgen:"tag,omitempty"`
···218 return nil, fmt.Errorf("failed to execute star-count query: %w ", err)
219 }
220221+ var repoDids []any
222+ repoDidToAt := make(map[string]syntax.ATURI)
223+ for atUri, r := range repoMap {
224+ if r.RepoDid != "" {
225+ repoDids = append(repoDids, r.RepoDid)
226+ repoDidToAt[r.RepoDid] = atUri
227+ }
228+ }
229+ if len(repoDids) > 0 {
230+ didInClause := strings.TrimSuffix(strings.Repeat("?, ", len(repoDids)), ", ")
231+ didStarQuery := fmt.Sprintf(
232+ `select subject_did, count(1)
233+ from stars
234+ where subject_did in (%s) and (subject_at is null or subject_at not in (%s))
235+ group by subject_did`,
236+ didInClause, inClause,
237+ )
238+ didStarArgs := append(repoDids, args...)
239+ rows, err = e.Query(didStarQuery, didStarArgs...)
240+ if err != nil {
241+ return nil, fmt.Errorf("failed to execute did-star-count query: %w ", err)
242+ }
243+ defer rows.Close()
244+245+ for rows.Next() {
246+ var subjectDid string
247+ var count int
248+ if err := rows.Scan(&subjectDid, &count); err != nil {
249+ log.Println("err", "err", err)
250+ continue
251+ }
252+ if atUri, ok := repoDidToAt[subjectDid]; ok {
253+ if r, ok := repoMap[atUri]; ok {
254+ r.RepoStats.StarCount += count
255+ }
256+ }
257+ }
258+ if err = rows.Err(); err != nil {
259+ return nil, fmt.Errorf("failed to execute did-star-count query: %w ", err)
260+ }
261+ }
262+263 issueCountQuery := fmt.Sprintf(
264 `select
265 repo_at,
···400 var nullableWebsite sql.NullString
401 var nullableTopicStr sql.NullString
402 var nullableRepoDid sql.NullString
403+ var nullableSource sql.NullString
404+ var nullableSpindle sql.NullString
405406+ row := e.QueryRow(`select id, did, name, knot, created, rkey, description, website, topics, source, spindle, repo_did from repos where at_uri = ?`, atUri)
407408 var createdAt string
409+ if err := row.Scan(&repo.Id, &repo.Did, &repo.Name, &repo.Knot, &createdAt, &repo.Rkey, &nullableDescription, &nullableWebsite, &nullableTopicStr, &nullableSource, &nullableSpindle, &nullableRepoDid); err != nil {
410 return nil, err
411 }
412 createdAtTime, _ := time.Parse(time.RFC3339, createdAt)
···421 if nullableTopicStr.Valid {
422 repo.Topics = strings.Fields(nullableTopicStr.String)
423 }
424+ if nullableSource.Valid {
425+ repo.Source = nullableSource.String
426+ }
427+ if nullableSpindle.Valid {
428+ repo.Spindle = nullableSpindle.String
429+ }
430 if nullableRepoDid.Valid {
431 repo.RepoDid = nullableRepoDid.String
432 }
···498 }
499 if err != nil {
500 return nil, err
501+ }
502+ if strings.HasPrefix(source, "did:") {
503+ return GetRepoByDid(e, source)
504 }
505 return GetRepoByAtUri(e, source)
506}
···687 whereClause = " where " + strings.Join(conditions, " and ")
688 }
689690+ query := fmt.Sprintf(`select id, repo_at, label_at, repo_did from repo_labels %s`, whereClause)
691692 rows, err := e.Query(query, args...)
693 if err != nil {
···698 var labels []models.RepoLabel
699 for rows.Next() {
700 var label models.RepoLabel
701+ var labelRepoDid sql.NullString
702703+ err := rows.Scan(&label.Id, &label.RepoAt, &label.LabelAt, &labelRepoDid)
704 if err != nil {
705 return nil, err
706+ }
707+ if labelRepoDid.Valid {
708+ label.RepoDid = labelRepoDid.String
709 }
710711 labels = append(labels, label)
+11
appview/db/star.go
···82 return stars, nil
83}
840000000000085// getStarStatuses returns a map of repo URIs to star status for a given user
86// This is an internal helper function to avoid N+1 queries
87func getStarStatuses(e Execer, userDid string, repoAts []syntax.ATURI) (map[string]bool, error) {
···82 return stars, nil
83}
8485+func GetStarCountByRepoDid(e Execer, repoDid string, repoAt syntax.ATURI) (int, error) {
86+ stars := 0
87+ err := e.QueryRow(
88+ `select count(did) from stars where subject_did = ? or subject_at = ?`,
89+ repoDid, repoAt.String()).Scan(&stars)
90+ if err != nil {
91+ return 0, err
92+ }
93+ return stars, nil
94+}
95+96// getStarStatuses returns a map of repo URIs to star status for a given user
97// This is an internal helper function to avoid N+1 queries
98func getStarStatuses(e Execer, userDid string, repoAts []syntax.ATURI) (map[string]bool, error) {
+15-5
appview/db/webhooks.go
···34 active,
35 events,
36 created_at,
37- updated_at
038 from webhooks
39 %s
40 order by created_at desc
···50 for rows.Next() {
51 var wh models.Webhook
52 var createdAt, updatedAt, eventsStr string
53- var secret sql.NullString
54 var active int
5556 err := rows.Scan(
···62 &eventsStr,
63 &createdAt,
64 &updatedAt,
065 )
66 if err != nil {
67 return nil, fmt.Errorf("failed to scan webhook: %w", err)
···80 }
81 if t, err := time.Parse(time.RFC3339, updatedAt); err == nil {
82 wh.UpdatedAt = t
00083 }
8485 webhooks = append(webhooks, wh)
···118 active = 1
119 }
12000000121 result, err := e.Exec(`
122- insert into webhooks (repo_at, url, secret, active, events)
123- values (?, ?, ?, ?, ?)
124- `, webhook.RepoAt.String(), webhook.Url, webhook.Secret, active, eventsStr)
125126 if err != nil {
127 return fmt.Errorf("failed to insert webhook: %w", err)
···34 active,
35 events,
36 created_at,
37+ updated_at,
38+ repo_did
39 from webhooks
40 %s
41 order by created_at desc
···51 for rows.Next() {
52 var wh models.Webhook
53 var createdAt, updatedAt, eventsStr string
54+ var secret, whRepoDid sql.NullString
55 var active int
5657 err := rows.Scan(
···63 &eventsStr,
64 &createdAt,
65 &updatedAt,
66+ &whRepoDid,
67 )
68 if err != nil {
69 return nil, fmt.Errorf("failed to scan webhook: %w", err)
···82 }
83 if t, err := time.Parse(time.RFC3339, updatedAt); err == nil {
84 wh.UpdatedAt = t
85+ }
86+ if whRepoDid.Valid {
87+ wh.RepoDid = whRepoDid.String
88 }
8990 webhooks = append(webhooks, wh)
···123 active = 1
124 }
125126+ var repoDid *string
127+ if webhook.RepoDid != "" {
128+ repoDid = &webhook.RepoDid
129+ }
130+131 result, err := e.Exec(`
132+ insert into webhooks (repo_at, url, secret, active, events, repo_did)
133+ values (?, ?, ?, ?, ?, ?)
134+ `, webhook.RepoAt.String(), webhook.Url, webhook.Secret, active, eventsStr, repoDid)
135136 if err != nil {
137 return fmt.Errorf("failed to insert webhook: %w", err)
···37 <p class="text-sm text-gray-500 dark:text-gray-400">A knot hosts repository data. <a href="/settings/knots" class="underline">Learn how to register your own knot.</a></p>
38 </fieldset>
3900000000000000000000040 <div class="space-y-2">
41 <button type="submit" class="btn-create flex items-center gap-2">
42 {{ i "git-fork" "w-4 h-4" }}
···37 <p class="text-sm text-gray-500 dark:text-gray-400">A knot hosts repository data. <a href="/settings/knots" class="underline">Learn how to register your own knot.</a></p>
38 </fieldset>
3940+ <fieldset class="space-y-3">
41+ <details>
42+ <summary class="dark:text-white cursor-pointer select-none">Bring your own DID</summary>
43+ <div class="mt-2">
44+ <input
45+ type="text"
46+ id="repo_did"
47+ name="repo_did"
48+ class="w-full p-2 border rounded bg-gray-100 dark:bg-gray-700 dark:text-white dark:border-gray-600"
49+ placeholder="did:web:example.com"
50+ />
51+ <p class="text-sm text-gray-500 dark:text-gray-400 mt-1">
52+ Provide a <code>did:web</code> you control to use as this fork's identity.
53+ You must serve a DID doc on your domain with an <code>atproto_pds</code> service
54+ endpoint pointing to the selected knot. If left empty, a <code>did:plc</code> will be
55+ automatically created for you!
56+ </p>
57+ </div>
58+ </details>
59+ </fieldset>
60+61 <div class="space-y-2">
62 <button type="submit" class="btn-create flex items-center gap-2">
63 {{ i "git-fork" "w-4 h-4" }}
+26
appview/pages/templates/repo/new.html
···70 <div class="space-y-2">
71 {{ template "defaultBranch" . }}
72 {{ template "knot" . }}
073 </div>
74 </div>
75 </div>
···168 A knot hosts repository data and handles Git operations.
169 You can also <a href="/settings/knots" class="underline">register your own knot</a>.
170 </p>
0000000000000000000000000171 </div>
172{{ end }}
173
···70 <div class="space-y-2">
71 {{ template "defaultBranch" . }}
72 {{ template "knot" . }}
73+ {{ template "repoDid" . }}
74 </div>
75 </div>
76 </div>
···169 A knot hosts repository data and handles Git operations.
170 You can also <a href="/settings/knots" class="underline">register your own knot</a>.
171 </p>
172+ </div>
173+{{ end }}
174+175+{{ define "repoDid" }}
176+ <div>
177+ <details>
178+ <summary class="text-sm font-bold uppercase dark:text-white mb-1 cursor-pointer select-none">
179+ Bring your own DID
180+ </summary>
181+ <div class="mt-2">
182+ <input
183+ type="text"
184+ id="repo_did"
185+ name="repo_did"
186+ class="w-full dark:bg-gray-700 dark:text-white dark:border-gray-600 border border-gray-300 rounded px-3 py-2"
187+ placeholder="did:web:example.com"
188+ />
189+ <p class="text-sm text-gray-500 dark:text-gray-400 mt-1">
190+ Provide a <code>did:web</code> you control to use as this repo's identity.
191+ You must serve a DID doc on your domain with an <code>atproto_pds</code> service
192+ endpoint pointing to the selected knot. If left empty, a <code>did:plc</code> will be
193+ automatically created for you!
194+ </p>
195+ </div>
196+ </details>
197 </div>
198{{ end }}
199