Write on the margins of the internet. Powered by the AT Protocol. margin.at
extension web atproto comments

fix OG

+63 -103
+2 -64
backend/cmd/server/main.go
··· 6 6 "net/http" 7 7 "os" 8 8 "os/signal" 9 - "path/filepath" 10 - "strings" 11 9 "syscall" 12 10 "time" 13 11 ··· 109 107 r.Get("/api/profile/{did}", handler.GetProfile) 110 108 r.Post("/api/profile/avatar", handler.UploadAvatar) 111 109 112 - staticDir := getEnv("STATIC_DIR", "../web/dist") 113 - serveStatic(r, staticDir) 114 - 115 - port := getEnv("PORT", "8080") 110 + port := getEnv("PORT", "8081") 116 111 server := &http.Server{ 117 112 Addr: ":" + port, 118 113 Handler: r, 119 114 } 120 115 121 - baseURL := getEnv("BASE_URL", "http://localhost:"+port) 122 116 go func() { 123 - log.Printf("🚀 Margin server running on %s", baseURL) 124 - log.Printf("📝 App: %s", baseURL) 125 - log.Printf("🔗 API: %s/api/annotations", baseURL) 117 + log.Printf("Margin API server running on :%s", port) 126 118 if err := server.ListenAndServe(); err != nil && err != http.ErrServerClosed { 127 119 log.Fatalf("Server error: %v", err) 128 120 } ··· 151 143 } 152 144 return fallback 153 145 } 154 - 155 - func serveStatic(r chi.Router, staticDir string) { 156 - absPath, err := filepath.Abs(staticDir) 157 - if err != nil { 158 - log.Printf("Warning: Could not resolve static directory: %v", err) 159 - return 160 - } 161 - 162 - if _, err := os.Stat(absPath); os.IsNotExist(err) { 163 - log.Printf("Warning: Static directory does not exist: %s", absPath) 164 - log.Printf("Run 'npm run build' in the web directory first") 165 - return 166 - } 167 - 168 - log.Printf("📂 Serving static files from: %s", absPath) 169 - 170 - fileServer := http.FileServer(http.Dir(absPath)) 171 - 172 - r.Get("/*", func(w http.ResponseWriter, req *http.Request) { 173 - path := req.URL.Path 174 - 175 - if strings.HasPrefix(path, "/api/") || strings.HasPrefix(path, "/auth/") { 176 - http.NotFound(w, req) 177 - return 178 - } 179 - 180 - filePath := filepath.Join(absPath, path) 181 - if _, err := os.Stat(filePath); err == nil { 182 - fileServer.ServeHTTP(w, req) 183 - return 184 - } 185 - 186 - if strings.HasPrefix(path, "/.well-known/") { 187 - http.NotFound(w, req) 188 - return 189 - } 190 - 191 - lastSlash := strings.LastIndex(path, "/") 192 - lastSegment := path 193 - if lastSlash >= 0 { 194 - lastSegment = path[lastSlash+1:] 195 - } 196 - 197 - staticExts := []string{".js", ".css", ".png", ".jpg", ".jpeg", ".gif", ".svg", ".ico", ".woff", ".woff2", ".ttf", ".eot", ".map"} 198 - for _, ext := range staticExts { 199 - if strings.HasSuffix(lastSegment, ext) { 200 - http.NotFound(w, req) 201 - return 202 - } 203 - } 204 - 205 - http.ServeFile(w, req, filepath.Join(absPath, "index.html")) 206 - }) 207 - }
+16 -10
web/src/lib/og.ts
··· 32 32 } 33 33 34 34 interface APIAnnotation { 35 - uri: string; 35 + id?: string; 36 + uri?: string; 36 37 author?: { did: string; handle?: string }; 37 38 creator?: { did: string; handle?: string }; 38 39 target?: { source?: string; title?: string; selector?: { exact?: string } }; ··· 50 51 } 51 52 52 53 interface APICollection { 53 - uri: string; 54 + id?: string; 55 + uri?: string; 54 56 name: string; 55 57 description?: string; 56 58 icon?: string; ··· 105 107 )) as APIAnnotation | null; 106 108 if (!item) return null; 107 109 110 + const itemURI = item.id || item.uri || uri; 108 111 const author = getAuthorHandle(item); 109 112 const source = item.target?.source || item.url || item.source || ""; 110 113 const domain = extractDomain(source); ··· 130 133 return { 131 134 title, 132 135 description, 133 - image: `${BASE_URL}/og-image?uri=${encodeURIComponent(item.uri)}`, 136 + image: `${BASE_URL}/og-image?uri=${encodeURIComponent(itemURI)}`, 134 137 author, 135 - pageURL: `${BASE_URL}/at/${encodeURIComponent(item.uri.slice(5))}`, 138 + pageURL: `${BASE_URL}/at/${encodeURIComponent(itemURI.slice(5))}`, 136 139 }; 137 140 } 138 141 ··· 142 145 )) as APIAnnotation | null; 143 146 if (!item) return null; 144 147 148 + const itemURI = item.id || item.uri || uri; 145 149 const author = getAuthorHandle(item); 146 150 const source = item.target?.source || item.url || item.source || ""; 147 151 const domain = extractDomain(source); ··· 164 168 return { 165 169 title, 166 170 description, 167 - image: `${BASE_URL}/og-image?uri=${encodeURIComponent(item.uri)}`, 171 + image: `${BASE_URL}/og-image?uri=${encodeURIComponent(itemURI)}`, 168 172 author, 169 - pageURL: `${BASE_URL}/at/${encodeURIComponent(item.uri.slice(5))}`, 173 + pageURL: `${BASE_URL}/at/${encodeURIComponent(itemURI.slice(5))}`, 170 174 }; 171 175 } 172 176 ··· 176 180 )) as APIAnnotation | null; 177 181 if (!item) return null; 178 182 183 + const itemURI = item.id || item.uri || uri; 179 184 const author = getAuthorHandle(item); 180 185 const source = item.target?.source || item.url || item.source || ""; 181 186 const domain = extractDomain(source); ··· 189 194 return { 190 195 title, 191 196 description, 192 - image: `${BASE_URL}/og-image?uri=${encodeURIComponent(item.uri)}`, 197 + image: `${BASE_URL}/og-image?uri=${encodeURIComponent(itemURI)}`, 193 198 author, 194 - pageURL: `${BASE_URL}/at/${encodeURIComponent(item.uri.slice(5))}`, 199 + pageURL: `${BASE_URL}/at/${encodeURIComponent(itemURI.slice(5))}`, 195 200 }; 196 201 } 197 202 ··· 201 206 )) as APICollection | null; 202 207 if (!item) return null; 203 208 209 + const itemURI = item.id || item.uri || uri; 204 210 const author = getAuthorHandle(item); 205 211 const icon = item.icon || "📁"; 206 212 const title = `${icon} ${item.name}`; ··· 215 221 return { 216 222 title, 217 223 description, 218 - image: `${BASE_URL}/og-image?uri=${encodeURIComponent(item.uri)}`, 224 + image: `${BASE_URL}/og-image?uri=${encodeURIComponent(itemURI)}`, 219 225 author, 220 - pageURL: `${BASE_URL}/collection/${encodeURIComponent(item.uri)}`, 226 + pageURL: `${BASE_URL}/collection/${encodeURIComponent(itemURI)}`, 221 227 }; 222 228 } 223 229
+11 -7
web/src/pages/[handle]/annotation/[rkey].astro
··· 11 11 let image = 'https://margin.at/og.png'; 12 12 13 13 if (handle && rkey) { 14 - const did = await resolveHandle(handle); 15 - if (did) { 16 - const data = await fetchOGForRoute(did, rkey, 'at.margin.annotation'); 17 - if (data) { 18 - title = data.title; 19 - description = data.description; 20 - image = data.image; 14 + try { 15 + const did = await resolveHandle(handle); 16 + if (did) { 17 + const data = await fetchOGForRoute(did, rkey, 'at.margin.annotation'); 18 + if (data) { 19 + title = data.title; 20 + description = data.description; 21 + image = data.image; 22 + } 21 23 } 24 + } catch (e) { 25 + console.error('OG fetch error (annotation):', e); 22 26 } 23 27 } 24 28 ---
+11 -7
web/src/pages/[handle]/bookmark/[rkey].astro
··· 11 11 let image = 'https://margin.at/og.png'; 12 12 13 13 if (handle && rkey) { 14 - const did = await resolveHandle(handle); 15 - if (did) { 16 - const data = await fetchOGForRoute(did, rkey, 'at.margin.bookmark'); 17 - if (data) { 18 - title = data.title; 19 - description = data.description; 20 - image = data.image; 14 + try { 15 + const did = await resolveHandle(handle); 16 + if (did) { 17 + const data = await fetchOGForRoute(did, rkey, 'at.margin.bookmark'); 18 + if (data) { 19 + title = data.title; 20 + description = data.description; 21 + image = data.image; 22 + } 21 23 } 24 + } catch (e) { 25 + console.error('OG fetch error (bookmark):', e); 22 26 } 23 27 } 24 28 ---
+12 -8
web/src/pages/[handle]/collection/[rkey].astro
··· 11 11 let image = 'https://margin.at/og.png'; 12 12 13 13 if (handle && rkey) { 14 - const did = await resolveHandle(handle); 15 - if (did) { 16 - const uri = `at://${did}/at.margin.collection/${rkey}`; 17 - const data = await fetchCollectionOG(uri); 18 - if (data) { 19 - title = data.title; 20 - description = data.description; 21 - image = data.image; 14 + try { 15 + const did = await resolveHandle(handle); 16 + if (did) { 17 + const uri = `at://${did}/at.margin.collection/${rkey}`; 18 + const data = await fetchCollectionOG(uri); 19 + if (data) { 20 + title = data.title; 21 + description = data.description; 22 + image = data.image; 23 + } 22 24 } 25 + } catch (e) { 26 + console.error('OG fetch error (collection):', e); 23 27 } 24 28 } 25 29 ---
+11 -7
web/src/pages/[handle]/highlight/[rkey].astro
··· 11 11 let image = 'https://margin.at/og.png'; 12 12 13 13 if (handle && rkey) { 14 - const did = await resolveHandle(handle); 15 - if (did) { 16 - const data = await fetchOGForRoute(did, rkey, 'at.margin.highlight'); 17 - if (data) { 18 - title = data.title; 19 - description = data.description; 20 - image = data.image; 14 + try { 15 + const did = await resolveHandle(handle); 16 + if (did) { 17 + const data = await fetchOGForRoute(did, rkey, 'at.margin.highlight'); 18 + if (data) { 19 + title = data.title; 20 + description = data.description; 21 + image = data.image; 22 + } 21 23 } 24 + } catch (e) { 25 + console.error('OG fetch error (highlight):', e); 22 26 } 23 27 } 24 28 ---