(READ ONLY) Margin is an open annotation layer for the internet. Powered by the AT Protocol. margin.at
extension web atproto comments
98
fork

Configure Feed

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

fix profile page

scanash00 9870ecb9 d76abc06

+63 -37
+36 -11
backend/internal/db/tags.go
··· 1 1 package db 2 2 3 - import "fmt" 4 - 5 3 type TrendingTag struct { 6 4 Tag string `json:"tag"` 7 5 Count int `json:"count"` 8 6 } 9 7 10 8 func (db *DB) GetTrendingTags(limit int) ([]TrendingTag, error) { 11 - query := ` 9 + var query string 10 + if db.driver == "postgres" { 11 + query = ` 12 + SELECT 13 + value as tag, 14 + COUNT(*) as count 15 + FROM annotations, json_array_elements_text(tags_json::json) as value 16 + WHERE tags_json IS NOT NULL 17 + AND tags_json != '' 18 + AND tags_json != '[]' 19 + AND created_at > NOW() - INTERVAL '7 days' 20 + GROUP BY tag 21 + HAVING count > 2 22 + ORDER BY count DESC 23 + LIMIT ? 24 + ` 25 + rows, err := db.Query(db.Rebind(query), limit) 26 + if err != nil { 27 + return nil, err 28 + } 29 + defer rows.Close() 30 + 31 + var tags []TrendingTag 32 + for rows.Next() { 33 + var t TrendingTag 34 + if err := rows.Scan(&t.Tag, &t.Count); err != nil { 35 + return nil, err 36 + } 37 + tags = append(tags, t) 38 + } 39 + return tags, nil 40 + } 41 + 42 + query = ` 12 43 SELECT 13 44 json_each.value as tag, 14 45 COUNT(*) as count ··· 16 47 WHERE tags_json IS NOT NULL 17 48 AND tags_json != '' 18 49 AND tags_json != '[]' 19 - AND created_at > %s 50 + AND created_at > datetime('now', '-7 days') 20 51 GROUP BY tag 21 52 HAVING count > 2 22 53 ORDER BY count DESC 23 54 LIMIT ? 24 55 ` 25 - 26 - dateFilter := "datetime('now', '-7 days')" 27 - if db.driver == "postgres" { 28 - dateFilter = "NOW() - INTERVAL '7 days'" 29 - } 30 - 31 - rows, err := db.Query(db.Rebind(fmt.Sprintf(query, dateFilter)), limit) 56 + rows, err := db.Query(db.Rebind(query), limit) 32 57 if err != nil { 33 58 return nil, err 34 59 }
+27 -26
web/src/pages/Profile.jsx
··· 81 81 const handle = routeHandle || user?.did || user?.handle; 82 82 const isOwnProfile = user && (user.did === handle || user.handle === handle); 83 83 84 - if (authLoading) { 85 - return ( 86 - <div className="profile-page"> 87 - <div className="feed"> 88 - {[1, 2, 3].map((i) => ( 89 - <div key={i} className="card"> 90 - <div 91 - className="skeleton skeleton-text" 92 - style={{ width: "40%" }} 93 - /> 94 - <div className="skeleton skeleton-text" /> 95 - <div 96 - className="skeleton skeleton-text" 97 - style={{ width: "60%" }} 98 - /> 99 - </div> 100 - ))} 101 - </div> 102 - </div> 103 - ); 104 - } 105 - 106 - if (!handle) { 107 - return <Navigate to="/login" replace />; 108 - } 109 - 110 84 useEffect(() => { 85 + if (!handle) return; 111 86 async function fetchProfile() { 112 87 try { 113 88 setLoading(true); ··· 202 177 alert("Failed to delete key: " + err.message); 203 178 } 204 179 }; 180 + 181 + if (authLoading) { 182 + return ( 183 + <div className="profile-page"> 184 + <div className="feed"> 185 + {[1, 2, 3].map((i) => ( 186 + <div key={i} className="card"> 187 + <div 188 + className="skeleton skeleton-text" 189 + style={{ width: "40%" }} 190 + /> 191 + <div className="skeleton skeleton-text" /> 192 + <div 193 + className="skeleton skeleton-text" 194 + style={{ width: "60%" }} 195 + /> 196 + </div> 197 + ))} 198 + </div> 199 + </div> 200 + ); 201 + } 202 + 203 + if (!handle) { 204 + return <Navigate to="/login" replace />; 205 + } 205 206 206 207 const displayName = profile?.displayName || profile?.handle || handle; 207 208 const displayHandle =