feat(download): add dynamic OG metadata and fix thumbnail caching
- Register a new route for box Open Graph images (`/d/{boxID}/og-image.jpg`).
- Dynamically set the download page title, description, and OG image URL based on box state (e.g., file count, expiration, password protection).
- Introduce `servePlaceholderThumbnail` to serve fallback thumbnails with `Cache-Control: no-store, must-revalidate`. This ensures the browser requests the real thumbnail once it is generated instead of caching the placeholder.
This commit is contained in:
@@ -76,9 +76,18 @@ func (a *App) DownloadPage(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
}
|
||||
|
||||
expiresLabel := box.ExpiresAt.Format("Jan 2, 2006 15:04 MST")
|
||||
title := "Shared files on Warpbox"
|
||||
description := fmt.Sprintf("%d file%s shared via Warpbox · expires %s", len(box.Files), plural(len(box.Files)), expiresLabel)
|
||||
if locked && box.Obfuscate {
|
||||
title = "Protected Warpbox link"
|
||||
description = "This shared box is password protected."
|
||||
}
|
||||
|
||||
a.renderPage(w, r, http.StatusOK, "download.html", web.PageData{
|
||||
Title: "Download files",
|
||||
Description: "Download files shared through Warpbox.",
|
||||
Title: title,
|
||||
Description: description,
|
||||
ImageURL: absoluteURL(r, fmt.Sprintf("/d/%s/og-image.jpg", box.ID)),
|
||||
Data: downloadPageData{
|
||||
Box: boxView{ID: box.ID},
|
||||
Files: files,
|
||||
@@ -87,11 +96,18 @@ func (a *App) DownloadPage(w http.ResponseWriter, r *http.Request) {
|
||||
Obfuscated: box.Obfuscate,
|
||||
DownloadCount: box.DownloadCount,
|
||||
MaxDownloads: box.MaxDownloads,
|
||||
ExpiresLabel: box.ExpiresAt.Format("Jan 2, 2006 15:04 MST"),
|
||||
ExpiresLabel: expiresLabel,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
func plural(n int) string {
|
||||
if n == 1 {
|
||||
return ""
|
||||
}
|
||||
return "s"
|
||||
}
|
||||
|
||||
func (a *App) DownloadFile(w http.ResponseWriter, r *http.Request) {
|
||||
box, file, ok := a.loadFileForRequest(w, r)
|
||||
if !ok {
|
||||
@@ -141,13 +157,18 @@ func (a *App) Thumbnail(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
if a.uploadService.IsProtected(box) && box.Obfuscate && !a.isBoxUnlocked(r, box) {
|
||||
http.ServeFile(w, r, filepath.Join(a.cfg.StaticDir, "img", "file-placeholder.webp"))
|
||||
a.servePlaceholderThumbnail(w, r)
|
||||
return
|
||||
}
|
||||
|
||||
object, err := a.uploadService.OpenThumbnailObject(r.Context(), box, file)
|
||||
if err != nil {
|
||||
http.ServeFile(w, r, filepath.Join(a.cfg.StaticDir, "img", "file-placeholder.webp"))
|
||||
// The thumbnail isn't generated yet (background job pending). Serve the
|
||||
// placeholder but mark it non-cacheable, otherwise the browser would
|
||||
// keep showing the placeholder until a hard refresh once the real
|
||||
// thumbnail lands. The real thumbnail below is content-stable, so it
|
||||
// gets a long immutable cache.
|
||||
a.servePlaceholderThumbnail(w, r)
|
||||
return
|
||||
}
|
||||
defer object.Body.Close()
|
||||
@@ -156,6 +177,14 @@ func (a *App) Thumbnail(w http.ResponseWriter, r *http.Request) {
|
||||
http.ServeContent(w, r, file.ID+"-thumbnail.jpg", object.ModTime, readSeekCloser(object.Body))
|
||||
}
|
||||
|
||||
// servePlaceholderThumbnail serves the fallback image with no-store so the
|
||||
// browser re-requests on the next load and picks up the real thumbnail as soon
|
||||
// as it has been generated.
|
||||
func (a *App) servePlaceholderThumbnail(w http.ResponseWriter, r *http.Request) {
|
||||
w.Header().Set("Cache-Control", "no-store, must-revalidate")
|
||||
http.ServeFile(w, r, filepath.Join(a.cfg.StaticDir, "img", "file-placeholder.webp"))
|
||||
}
|
||||
|
||||
func (a *App) UnlockBox(w http.ResponseWriter, r *http.Request) {
|
||||
box, err := a.uploadService.GetBox(r.PathValue("boxID"))
|
||||
if err != nil {
|
||||
|
||||
Reference in New Issue
Block a user