diff --git a/models/blog/blog_post.go b/models/blog/blog_post.go index 3362d67106..b6bb08484f 100644 --- a/models/blog/blog_post.go +++ b/models/blog/blog_post.go @@ -420,6 +420,14 @@ func HasSubscriptionOnlyBlogPosts(ctx context.Context, repoID int64) (bool, erro return count > 0, err } +// IsPublishedBlogFeaturedImage checks if the given attachment ID is used as a +// featured image by any published blog post. +func IsPublishedBlogFeaturedImage(ctx context.Context, attachmentID int64) (bool, error) { + return db.GetEngine(ctx). + Where("featured_image_id = ? AND status >= ?", attachmentID, BlogPostPublic). + Exist(new(BlogPost)) +} + // CreateBlogPost inserts a new blog post. func CreateBlogPost(ctx context.Context, p *BlogPost) error { _, err := db.GetEngine(ctx).Insert(p) diff --git a/routers/web/repo/attachment.go b/routers/web/repo/attachment.go index 47742be2d8..54db8f131a 100644 --- a/routers/web/repo/attachment.go +++ b/routers/web/repo/attachment.go @@ -7,6 +7,7 @@ import ( "fmt" "net/http" + blog_model "code.gitcaddy.com/server/v3/models/blog" access_model "code.gitcaddy.com/server/v3/models/perm/access" repo_model "code.gitcaddy.com/server/v3/models/repo" "code.gitcaddy.com/server/v3/models/unit" @@ -122,8 +123,12 @@ func ServeAttachment(ctx *context.Context, uuid string) { return } if !perm.CanRead(unit.TypeCode) { - ctx.HTTPError(http.StatusNotFound) - return + // Allow public access to blog featured images when the repo has blog enabled, + // even if the repo is private. This supports public landing pages with blog sections. + if !isBlogFeaturedImage(ctx, attach) { + ctx.HTTPError(http.StatusNotFound) + return + } } } else if !(ctx.IsSigned && attach.UploaderID == ctx.Doer.ID) { // We block if not the uploader ctx.HTTPError(http.StatusNotFound) @@ -171,6 +176,26 @@ func ServeAttachment(ctx *context.Context, uuid string) { common.ServeContentByReadSeeker(ctx.Base, attach.Name, util.ToPointer(attach.CreatedUnix.AsTime()), fr) } +// isBlogFeaturedImage checks if the attachment is used as a featured image for a +// published blog post on a repo that has blog enabled. This allows public access +// to these images even when the repo is private, supporting public landing pages. +func isBlogFeaturedImage(ctx *context.Context, attach *repo_model.Attachment) bool { + if attach.RepoID == 0 { + return false + } + repo, err := repo_model.GetRepositoryByID(ctx, attach.RepoID) + if err != nil || !repo.BlogEnabled { + return false + } + // Check if any published blog post uses this attachment as its featured image + has, err := blog_model.IsPublishedBlogFeaturedImage(ctx, attach.ID) + if err != nil { + log.Warn("isBlogFeaturedImage: %v", err) + return false + } + return has +} + // GetAttachment serve attachments func GetAttachment(ctx *context.Context) { ServeAttachment(ctx, ctx.PathParam("uuid")) diff --git a/templates/pages/bold-marketing.tmpl b/templates/pages/bold-marketing.tmpl index b8a9a2ad9f..fb53f8f2df 100644 --- a/templates/pages/bold-marketing.tmpl +++ b/templates/pages/bold-marketing.tmpl @@ -1201,7 +1201,7 @@ {{if .BlogTags}}·{{range .BlogTags}}{{.}} {{end}}{{end}}