···7070 IconURL string
7171 StarCount int
7272 PullCount int
7373+ IsStarred bool // Whether the current user has starred this repository
7374 CreatedAt time.Time
7475 HoldEndpoint string // Hold endpoint for health checking
7576 Reachable bool // Whether the hold endpoint is reachable
···114115 IconURL string
115116 StarCount int
116117 PullCount int
118118+ IsStarred bool // Whether the current user has starred this repository
117119}
118120119121// RepositoryWithStats combines repository data with statistics
···131133 IconURL string
132134 StarCount int
133135 PullCount int
136136+ IsStarred bool // Whether the current user has starred this repository
134137}
135138136139// PlatformInfo represents platform information (OS/Architecture)
+19-10
pkg/appview/db/queries.go
···3131}
32323333// GetRecentPushes fetches recent pushes with pagination
3434-func GetRecentPushes(db *sql.DB, limit, offset int, userFilter string) ([]Push, int, error) {
3434+func GetRecentPushes(db *sql.DB, limit, offset int, userFilter string, currentUserDID string) ([]Push, int, error) {
3535 query := `
3636 SELECT
3737 u.did,
···4444 COALESCE((SELECT value FROM repository_annotations WHERE did = u.did AND repository = t.repository AND key = 'io.atcr.icon'), ''),
4545 COALESCE(rs.pull_count, 0),
4646 COALESCE((SELECT COUNT(*) FROM stars WHERE owner_did = u.did AND repository = t.repository), 0),
4747+ COALESCE((SELECT COUNT(*) FROM stars WHERE starrer_did = ? AND owner_did = u.did AND repository = t.repository), 0),
4748 t.created_at,
4849 m.hold_endpoint
4950 FROM tags t
···5253 LEFT JOIN repository_stats rs ON t.did = rs.did AND t.repository = rs.repository
5354 `
54555555- args := []any{}
5656+ args := []any{currentUserDID}
56575758 if userFilter != "" {
5859 query += " WHERE u.handle = ? OR u.did = ?"
···7172 var pushes []Push
7273 for rows.Next() {
7374 var p Push
7474- if err := rows.Scan(&p.DID, &p.Handle, &p.Repository, &p.Tag, &p.Digest, &p.Title, &p.Description, &p.IconURL, &p.PullCount, &p.StarCount, &p.CreatedAt, &p.HoldEndpoint); err != nil {
7575+ var isStarredInt int
7676+ if err := rows.Scan(&p.DID, &p.Handle, &p.Repository, &p.Tag, &p.Digest, &p.Title, &p.Description, &p.IconURL, &p.PullCount, &p.StarCount, &isStarredInt, &p.CreatedAt, &p.HoldEndpoint); err != nil {
7577 return nil, 0, err
7678 }
7979+ p.IsStarred = isStarredInt > 0
7780 pushes = append(pushes, p)
7881 }
7982···9598}
969997100// SearchPushes searches for pushes matching the query across handles, DIDs, repositories, and annotations
9898-func SearchPushes(db *sql.DB, query string, limit, offset int) ([]Push, int, error) {
101101+func SearchPushes(db *sql.DB, query string, limit, offset int, currentUserDID string) ([]Push, int, error) {
99102 // Escape LIKE wildcards so they're treated literally
100103 query = escapeLikePattern(query)
101104···114117 COALESCE((SELECT value FROM repository_annotations WHERE did = u.did AND repository = t.repository AND key = 'io.atcr.icon'), ''),
115118 COALESCE(rs.pull_count, 0),
116119 COALESCE((SELECT COUNT(*) FROM stars WHERE owner_did = u.did AND repository = t.repository), 0),
120120+ COALESCE((SELECT COUNT(*) FROM stars WHERE starrer_did = ? AND owner_did = u.did AND repository = t.repository), 0),
117121 t.created_at,
118122 m.hold_endpoint
119123 FROM tags t
···132136 LIMIT ? OFFSET ?
133137 `
134138135135- rows, err := db.Query(sqlQuery, searchPattern, query, searchPattern, searchPattern, limit, offset)
139139+ rows, err := db.Query(sqlQuery, currentUserDID, searchPattern, query, searchPattern, searchPattern, limit, offset)
136140 if err != nil {
137141 return nil, 0, err
138142 }
···141145 var pushes []Push
142146 for rows.Next() {
143147 var p Push
144144- if err := rows.Scan(&p.DID, &p.Handle, &p.Repository, &p.Tag, &p.Digest, &p.Title, &p.Description, &p.IconURL, &p.PullCount, &p.StarCount, &p.CreatedAt, &p.HoldEndpoint); err != nil {
148148+ var isStarredInt int
149149+ if err := rows.Scan(&p.DID, &p.Handle, &p.Repository, &p.Tag, &p.Digest, &p.Title, &p.Description, &p.IconURL, &p.PullCount, &p.StarCount, &isStarredInt, &p.CreatedAt, &p.HoldEndpoint); err != nil {
145150 return nil, 0, err
146151 }
152152+ p.IsStarred = isStarredInt > 0
147153 pushes = append(pushes, p)
148154 }
149155···15711577}
1572157815731579// GetFeaturedRepositories fetches top repositories sorted by stars and pulls
15741574-func GetFeaturedRepositories(db *sql.DB, limit int) ([]FeaturedRepository, error) {
15801580+func GetFeaturedRepositories(db *sql.DB, limit int, currentUserDID string) ([]FeaturedRepository, error) {
15751581 query := `
15761582 WITH latest_manifests AS (
15771583 SELECT did, repository, MAX(id) as latest_id
···15961602 COALESCE((SELECT value FROM repository_annotations WHERE did = m.did AND repository = m.repository AND key = 'org.opencontainers.image.description'), ''),
15971603 COALESCE((SELECT value FROM repository_annotations WHERE did = m.did AND repository = m.repository AND key = 'io.atcr.icon'), ''),
15981604 rs.pull_count,
15991599- rs.star_count
16051605+ rs.star_count,
16061606+ COALESCE((SELECT COUNT(*) FROM stars WHERE starrer_did = ? AND owner_did = m.did AND repository = m.repository), 0)
16001607 FROM latest_manifests lm
16011608 JOIN manifests m ON lm.latest_id = m.id
16021609 JOIN users u ON m.did = u.did
···16051612 LIMIT ?
16061613 `
1607161416081608- rows, err := db.Query(query, limit)
16151615+ rows, err := db.Query(query, currentUserDID, limit)
16091616 if err != nil {
16101617 return nil, err
16111618 }
···16141621 var featured []FeaturedRepository
16151622 for rows.Next() {
16161623 var f FeaturedRepository
16241624+ var isStarredInt int
1617162516181626 if err := rows.Scan(&f.OwnerDID, &f.OwnerHandle, &f.Repository,
16191619- &f.Title, &f.Description, &f.IconURL, &f.PullCount, &f.StarCount); err != nil {
16271627+ &f.Title, &f.Description, &f.IconURL, &f.PullCount, &f.StarCount, &isStarredInt); err != nil {
16201628 return nil, err
16211629 }
16301630+ f.IsStarred = isStarredInt > 0
1622163116231632 featured = append(featured, f)
16241633 }
+16-2
pkg/appview/handlers/home.go
···11111212 "atcr.io/pkg/appview/db"
1313 "atcr.io/pkg/appview/holdhealth"
1414+ "atcr.io/pkg/appview/middleware"
1415)
15161617// HomeHandler handles the home page
···2122}
22232324func (h *HomeHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
2525+ // Get current user DID (empty string if not logged in)
2626+ var currentUserDID string
2727+ if user := middleware.GetUser(r); user != nil {
2828+ currentUserDID = user.DID
2929+ }
3030+2431 // Fetch featured repositories (top 6)
2525- featured, err := db.GetFeaturedRepositories(h.DB, 6)
3232+ featured, err := db.GetFeaturedRepositories(h.DB, 6, currentUserDID)
2633 if err != nil {
2734 // Log error but continue - featured section will be empty
2835 featured = []db.FeaturedRepository{}
···3946 IconURL: repo.IconURL,
4047 StarCount: repo.StarCount,
4148 PullCount: repo.PullCount,
4949+ IsStarred: repo.IsStarred,
4250 }
4351 }
4452···7785 userFilter = r.URL.Query().Get("q")
7886 }
79878080- pushes, total, err := db.GetRecentPushes(h.DB, limit, offset, userFilter)
8888+ // Get current user DID (empty string if not logged in)
8989+ var currentUserDID string
9090+ if user := middleware.GetUser(r); user != nil {
9191+ currentUserDID = user.DID
9292+ }
9393+9494+ pushes, total, err := db.GetRecentPushes(h.DB, limit, offset, userFilter, currentUserDID)
8195 if err != nil {
8296 http.Error(w, err.Error(), http.StatusInternalServerError)
8397 return
+8-1
pkg/appview/handlers/search.go
···88 "strings"
991010 "atcr.io/pkg/appview/db"
1111+ "atcr.io/pkg/appview/middleware"
1112)
12131314// SearchHandler handles the search page
···7778 offset, _ = strconv.Atoi(o)
7879 }
79808080- pushes, total, err := db.SearchPushes(h.DB, query, limit, offset)
8181+ // Get current user DID (empty string if not logged in)
8282+ var currentUserDID string
8383+ if user := middleware.GetUser(r); user != nil {
8484+ currentUserDID = user.DID
8585+ }
8686+8787+ pushes, total, err := db.SearchPushes(h.DB, query, limit, offset, currentUserDID)
8188 if err != nil {
8289 http.Error(w, err.Error(), http.StatusInternalServerError)
8390 return