From 628bdf3c4066c4701c7b74a279f1806b10238520 Mon Sep 17 00:00:00 2001 From: Patrick Britton Date: Sun, 8 Feb 2026 14:33:48 -0600 Subject: [PATCH] fix: add 3s timeout to sync link preview fetch, async fallback --- go-backend/internal/handlers/post_handler.go | 22 +++++++++++++++++--- 1 file changed, 19 insertions(+), 3 deletions(-) diff --git a/go-backend/internal/handlers/post_handler.go b/go-backend/internal/handlers/post_handler.go index 2e2ceba..ac1ecce 100644 --- a/go-backend/internal/handlers/post_handler.go +++ b/go-backend/internal/handlers/post_handler.go @@ -575,15 +575,20 @@ func (h *PostHandler) CreatePost(c *gin.Context) { h.moderationService.LogAIDecision(c.Request.Context(), "post", post.ID, userID, req.Body, scores, nil, decision, flagReason, orDecision, nil) } - // Auto-extract link preview from post body (synchronous so it's in the response) + // Auto-extract link preview from post body + // Try synchronously with a short timeout so the response isn't blocked. + // If it times out, fall back to async so it's saved for later views. if h.linkPreviewService != nil { linkURL := services.ExtractFirstURL(req.Body) if linkURL != "" { - // Check if author is an official account (trusted = skip safety checks) var isOfficial bool _ = h.postRepo.Pool().QueryRow(c.Request.Context(), `SELECT COALESCE(is_official, false) FROM profiles WHERE id = $1`, userID).Scan(&isOfficial) - lp, lpErr := h.linkPreviewService.FetchPreview(c.Request.Context(), linkURL, isOfficial) + // 3-second budget for synchronous fetch + fastCtx, fastCancel := context.WithTimeout(c.Request.Context(), 3*time.Second) + lp, lpErr := h.linkPreviewService.FetchPreview(fastCtx, linkURL, isOfficial) + fastCancel() + if lpErr == nil && lp != nil { _ = h.linkPreviewService.SaveLinkPreview(c.Request.Context(), post.ID.String(), lp) post.LinkPreviewURL = &lp.URL @@ -591,6 +596,17 @@ func (h *PostHandler) CreatePost(c *gin.Context) { post.LinkPreviewDescription = &lp.Description post.LinkPreviewImageURL = &lp.ImageURL post.LinkPreviewSiteName = &lp.SiteName + } else if lpErr != nil { + // Timed out or failed — fire async so it's saved for later views + postID := post.ID.String() + go func() { + bgCtx, bgCancel := context.WithTimeout(context.Background(), 10*time.Second) + defer bgCancel() + bgLp, bgErr := h.linkPreviewService.FetchPreview(bgCtx, linkURL, isOfficial) + if bgErr == nil && bgLp != nil { + _ = h.linkPreviewService.SaveLinkPreview(bgCtx, postID, bgLp) + } + }() } } }