refactor(pages): deduplicate image upload/delete handlers
Some checks failed
Build and Release / Create Release (push) Successful in 0s
Build and Release / Integration Tests (PostgreSQL) (push) Successful in 2m51s
Build and Release / Unit Tests (push) Successful in 8m56s
Build and Release / Lint (push) Successful in 9m26s
Build and Release / Build Binaries (amd64, darwin, macos) (push) Failing after 1s
Build and Release / Build Binaries (arm64, darwin, macos) (push) Failing after 0s
Build and Release / Build Binaries (amd64, linux, linux-latest) (push) Successful in 4m50s
Build and Release / Build Binaries (amd64, windows, windows-latest) (push) Successful in 9h7m28s
Build and Release / Build Binary (linux/arm64) (push) Successful in 8m1s
Some checks failed
Build and Release / Create Release (push) Successful in 0s
Build and Release / Integration Tests (PostgreSQL) (push) Successful in 2m51s
Build and Release / Unit Tests (push) Successful in 8m56s
Build and Release / Lint (push) Successful in 9m26s
Build and Release / Build Binaries (amd64, darwin, macos) (push) Failing after 1s
Build and Release / Build Binaries (arm64, darwin, macos) (push) Failing after 0s
Build and Release / Build Binaries (amd64, linux, linux-latest) (push) Successful in 4m50s
Build and Release / Build Binaries (amd64, windows, windows-latest) (push) Successful in 9h7m28s
Build and Release / Build Binary (linux/arm64) (push) Successful in 8m1s
Extract common image upload and delete logic into pagesUploadImage and pagesDeleteImage helper functions. Eliminates ~90 lines of duplicated code across logo, favicon, and hero image handlers. Uses function parameters for field-specific operations (get/set config fields, locale keys, file prefixes). Makes it easier to add new image upload fields in the future.
This commit is contained in:
@@ -261,23 +261,33 @@ func PagesBrandPost(ctx *context.Context) {
|
|||||||
ctx.Redirect(ctx.Repo.Repository.Link() + "/settings/pages/brand")
|
ctx.Redirect(ctx.Repo.Repository.Link() + "/settings/pages/brand")
|
||||||
}
|
}
|
||||||
|
|
||||||
const maxBrandImageSize = 5 * 1024 * 1024 // 5 MB
|
const maxPagesImageSize = 5 * 1024 * 1024 // 5 MB
|
||||||
|
|
||||||
// PagesBrandUploadLogo handles logo image file upload.
|
// pagesUploadImage is a shared helper for uploading images to pages config fields.
|
||||||
func PagesBrandUploadLogo(ctx *context.Context) {
|
// formField is the multipart form field name, filePrefix is used in the stored filename,
|
||||||
|
// redirectPath is the settings page to redirect to, getOld/setNew read/write the config field,
|
||||||
|
// and successKey is the locale key for the success flash message.
|
||||||
|
func pagesUploadImage(
|
||||||
|
ctx *context.Context,
|
||||||
|
formField, filePrefix, redirectPath string,
|
||||||
|
getOld func(*pages_module.LandingConfig) string,
|
||||||
|
setNew func(*pages_module.LandingConfig, string),
|
||||||
|
errorKey, tooLargeKey, notImageKey, successKey string,
|
||||||
|
) {
|
||||||
repo := ctx.Repo.Repository
|
repo := ctx.Repo.Repository
|
||||||
|
redirect := repo.Link() + redirectPath
|
||||||
|
|
||||||
file, header, err := ctx.Req.FormFile("brand_logo")
|
file, header, err := ctx.Req.FormFile(formField)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ctx.Flash.Error(ctx.Tr("repo.settings.pages.brand_upload_error"))
|
ctx.Flash.Error(ctx.Tr(errorKey))
|
||||||
ctx.Redirect(repo.Link() + "/settings/pages/brand")
|
ctx.Redirect(redirect)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
defer file.Close()
|
defer file.Close()
|
||||||
|
|
||||||
if header.Size > maxBrandImageSize {
|
if header.Size > maxPagesImageSize {
|
||||||
ctx.Flash.Error(ctx.Tr("repo.settings.pages.brand_image_too_large"))
|
ctx.Flash.Error(ctx.Tr(tooLargeKey))
|
||||||
ctx.Redirect(repo.Link() + "/settings/pages/brand")
|
ctx.Redirect(redirect)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -289,18 +299,18 @@ func PagesBrandUploadLogo(ctx *context.Context) {
|
|||||||
|
|
||||||
st := typesniffer.DetectContentType(data)
|
st := typesniffer.DetectContentType(data)
|
||||||
if !(st.IsImage() && !st.IsSvgImage()) {
|
if !(st.IsImage() && !st.IsSvgImage()) {
|
||||||
ctx.Flash.Error(ctx.Tr("repo.settings.pages.brand_not_an_image"))
|
ctx.Flash.Error(ctx.Tr(notImageKey))
|
||||||
ctx.Redirect(repo.Link() + "/settings/pages/brand")
|
ctx.Redirect(redirect)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
hash := sha256.Sum256(data)
|
hash := sha256.Sum256(data)
|
||||||
filename := fmt.Sprintf("pages-logo-%d-%x", repo.ID, hash[:8])
|
filename := fmt.Sprintf("%s-%d-%x", filePrefix, repo.ID, hash[:8])
|
||||||
|
|
||||||
config := getPagesLandingConfig(ctx)
|
config := getPagesLandingConfig(ctx)
|
||||||
|
|
||||||
if config.Brand.UploadedLogo != "" {
|
if old := getOld(config); old != "" {
|
||||||
_ = storage.RepoAvatars.Delete(config.Brand.UploadedLogo)
|
_ = storage.RepoAvatars.Delete(old)
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := storage.SaveFrom(storage.RepoAvatars, filename, func(w io.Writer) error {
|
if err := storage.SaveFrom(storage.RepoAvatars, filename, func(w io.Writer) error {
|
||||||
@@ -311,108 +321,72 @@ func PagesBrandUploadLogo(ctx *context.Context) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
config.Brand.UploadedLogo = filename
|
setNew(config, filename)
|
||||||
if err := savePagesLandingConfig(ctx, config); err != nil {
|
if err := savePagesLandingConfig(ctx, config); err != nil {
|
||||||
ctx.ServerError("SavePagesConfig", err)
|
ctx.ServerError("SavePagesConfig", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
ctx.Flash.Success(ctx.Tr("repo.settings.pages.brand_logo_uploaded"))
|
ctx.Flash.Success(ctx.Tr(successKey))
|
||||||
ctx.Redirect(repo.Link() + "/settings/pages/brand")
|
ctx.Redirect(redirect)
|
||||||
|
}
|
||||||
|
|
||||||
|
// pagesDeleteImage is a shared helper for deleting uploaded images from pages config fields.
|
||||||
|
func pagesDeleteImage(
|
||||||
|
ctx *context.Context,
|
||||||
|
redirectPath string,
|
||||||
|
getOld func(*pages_module.LandingConfig) string,
|
||||||
|
clearField func(*pages_module.LandingConfig),
|
||||||
|
successKey string,
|
||||||
|
) {
|
||||||
|
repo := ctx.Repo.Repository
|
||||||
|
config := getPagesLandingConfig(ctx)
|
||||||
|
|
||||||
|
if old := getOld(config); old != "" {
|
||||||
|
_ = storage.RepoAvatars.Delete(old)
|
||||||
|
clearField(config)
|
||||||
|
if err := savePagesLandingConfig(ctx, config); err != nil {
|
||||||
|
ctx.ServerError("SavePagesConfig", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx.Flash.Success(ctx.Tr(successKey))
|
||||||
|
ctx.Redirect(repo.Link() + redirectPath)
|
||||||
|
}
|
||||||
|
|
||||||
|
// PagesBrandUploadLogo handles logo image file upload.
|
||||||
|
func PagesBrandUploadLogo(ctx *context.Context) {
|
||||||
|
pagesUploadImage(ctx, "brand_logo", "pages-logo", "/settings/pages/brand",
|
||||||
|
func(c *pages_module.LandingConfig) string { return c.Brand.UploadedLogo },
|
||||||
|
func(c *pages_module.LandingConfig, f string) { c.Brand.UploadedLogo = f },
|
||||||
|
"repo.settings.pages.brand_upload_error", "repo.settings.pages.brand_image_too_large",
|
||||||
|
"repo.settings.pages.brand_not_an_image", "repo.settings.pages.brand_logo_uploaded")
|
||||||
}
|
}
|
||||||
|
|
||||||
// PagesBrandDeleteLogo removes the uploaded logo image.
|
// PagesBrandDeleteLogo removes the uploaded logo image.
|
||||||
func PagesBrandDeleteLogo(ctx *context.Context) {
|
func PagesBrandDeleteLogo(ctx *context.Context) {
|
||||||
repo := ctx.Repo.Repository
|
pagesDeleteImage(ctx, "/settings/pages/brand",
|
||||||
config := getPagesLandingConfig(ctx)
|
func(c *pages_module.LandingConfig) string { return c.Brand.UploadedLogo },
|
||||||
|
func(c *pages_module.LandingConfig) { c.Brand.UploadedLogo = "" },
|
||||||
if config.Brand.UploadedLogo != "" {
|
"repo.settings.pages.brand_logo_deleted")
|
||||||
_ = storage.RepoAvatars.Delete(config.Brand.UploadedLogo)
|
|
||||||
config.Brand.UploadedLogo = ""
|
|
||||||
if err := savePagesLandingConfig(ctx, config); err != nil {
|
|
||||||
ctx.ServerError("SavePagesConfig", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ctx.Flash.Success(ctx.Tr("repo.settings.pages.brand_logo_deleted"))
|
|
||||||
ctx.Redirect(repo.Link() + "/settings/pages/brand")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// PagesBrandUploadFavicon handles favicon file upload.
|
// PagesBrandUploadFavicon handles favicon file upload.
|
||||||
func PagesBrandUploadFavicon(ctx *context.Context) {
|
func PagesBrandUploadFavicon(ctx *context.Context) {
|
||||||
repo := ctx.Repo.Repository
|
pagesUploadImage(ctx, "brand_favicon", "pages-favicon", "/settings/pages/brand",
|
||||||
|
func(c *pages_module.LandingConfig) string { return c.Brand.UploadedFavicon },
|
||||||
file, header, err := ctx.Req.FormFile("brand_favicon")
|
func(c *pages_module.LandingConfig, f string) { c.Brand.UploadedFavicon = f },
|
||||||
if err != nil {
|
"repo.settings.pages.brand_upload_error", "repo.settings.pages.brand_image_too_large",
|
||||||
ctx.Flash.Error(ctx.Tr("repo.settings.pages.brand_upload_error"))
|
"repo.settings.pages.brand_not_an_image", "repo.settings.pages.brand_favicon_uploaded")
|
||||||
ctx.Redirect(repo.Link() + "/settings/pages/brand")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
defer file.Close()
|
|
||||||
|
|
||||||
if header.Size > maxBrandImageSize {
|
|
||||||
ctx.Flash.Error(ctx.Tr("repo.settings.pages.brand_image_too_large"))
|
|
||||||
ctx.Redirect(repo.Link() + "/settings/pages/brand")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
data, err := io.ReadAll(file)
|
|
||||||
if err != nil {
|
|
||||||
ctx.ServerError("ReadAll", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
st := typesniffer.DetectContentType(data)
|
|
||||||
if !(st.IsImage() && !st.IsSvgImage()) {
|
|
||||||
ctx.Flash.Error(ctx.Tr("repo.settings.pages.brand_not_an_image"))
|
|
||||||
ctx.Redirect(repo.Link() + "/settings/pages/brand")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
hash := sha256.Sum256(data)
|
|
||||||
filename := fmt.Sprintf("pages-favicon-%d-%x", repo.ID, hash[:8])
|
|
||||||
|
|
||||||
config := getPagesLandingConfig(ctx)
|
|
||||||
|
|
||||||
if config.Brand.UploadedFavicon != "" {
|
|
||||||
_ = storage.RepoAvatars.Delete(config.Brand.UploadedFavicon)
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := storage.SaveFrom(storage.RepoAvatars, filename, func(w io.Writer) error {
|
|
||||||
_, err := w.Write(data)
|
|
||||||
return err
|
|
||||||
}); err != nil {
|
|
||||||
ctx.ServerError("SaveFrom", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
config.Brand.UploadedFavicon = filename
|
|
||||||
if err := savePagesLandingConfig(ctx, config); err != nil {
|
|
||||||
ctx.ServerError("SavePagesConfig", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
ctx.Flash.Success(ctx.Tr("repo.settings.pages.brand_favicon_uploaded"))
|
|
||||||
ctx.Redirect(repo.Link() + "/settings/pages/brand")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// PagesBrandDeleteFavicon removes the uploaded favicon.
|
// PagesBrandDeleteFavicon removes the uploaded favicon.
|
||||||
func PagesBrandDeleteFavicon(ctx *context.Context) {
|
func PagesBrandDeleteFavicon(ctx *context.Context) {
|
||||||
repo := ctx.Repo.Repository
|
pagesDeleteImage(ctx, "/settings/pages/brand",
|
||||||
config := getPagesLandingConfig(ctx)
|
func(c *pages_module.LandingConfig) string { return c.Brand.UploadedFavicon },
|
||||||
|
func(c *pages_module.LandingConfig) { c.Brand.UploadedFavicon = "" },
|
||||||
if config.Brand.UploadedFavicon != "" {
|
"repo.settings.pages.brand_favicon_deleted")
|
||||||
_ = storage.RepoAvatars.Delete(config.Brand.UploadedFavicon)
|
|
||||||
config.Brand.UploadedFavicon = ""
|
|
||||||
if err := savePagesLandingConfig(ctx, config); err != nil {
|
|
||||||
ctx.ServerError("SavePagesConfig", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ctx.Flash.Success(ctx.Tr("repo.settings.pages.brand_favicon_deleted"))
|
|
||||||
ctx.Redirect(repo.Link() + "/settings/pages/brand")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func PagesHero(ctx *context.Context) {
|
func PagesHero(ctx *context.Context) {
|
||||||
@@ -444,83 +418,21 @@ func PagesHeroPost(ctx *context.Context) {
|
|||||||
ctx.Redirect(ctx.Repo.Repository.Link() + "/settings/pages/hero")
|
ctx.Redirect(ctx.Repo.Repository.Link() + "/settings/pages/hero")
|
||||||
}
|
}
|
||||||
|
|
||||||
const maxHeroImageSize = 5 * 1024 * 1024 // 5MB
|
|
||||||
|
|
||||||
// PagesHeroUploadImage handles hero image file upload.
|
// PagesHeroUploadImage handles hero image file upload.
|
||||||
func PagesHeroUploadImage(ctx *context.Context) {
|
func PagesHeroUploadImage(ctx *context.Context) {
|
||||||
repo := ctx.Repo.Repository
|
pagesUploadImage(ctx, "hero_image", "pages-hero", "/settings/pages/hero",
|
||||||
|
func(c *pages_module.LandingConfig) string { return c.Hero.UploadedImage },
|
||||||
file, header, err := ctx.Req.FormFile("hero_image")
|
func(c *pages_module.LandingConfig, f string) { c.Hero.UploadedImage = f },
|
||||||
if err != nil {
|
"repo.settings.pages.hero_upload_error", "repo.settings.pages.hero_image_too_large",
|
||||||
ctx.Flash.Error(ctx.Tr("repo.settings.pages.hero_upload_error"))
|
"repo.settings.pages.hero_not_an_image", "repo.settings.pages.hero_image_uploaded")
|
||||||
ctx.Redirect(repo.Link() + "/settings/pages/hero")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
defer file.Close()
|
|
||||||
|
|
||||||
if header.Size > maxHeroImageSize {
|
|
||||||
ctx.Flash.Error(ctx.Tr("repo.settings.pages.hero_image_too_large"))
|
|
||||||
ctx.Redirect(repo.Link() + "/settings/pages/hero")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
data, err := io.ReadAll(file)
|
|
||||||
if err != nil {
|
|
||||||
ctx.ServerError("ReadAll", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
st := typesniffer.DetectContentType(data)
|
|
||||||
if !(st.IsImage() && !st.IsSvgImage()) {
|
|
||||||
ctx.Flash.Error(ctx.Tr("repo.settings.pages.hero_not_an_image"))
|
|
||||||
ctx.Redirect(repo.Link() + "/settings/pages/hero")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
hash := sha256.Sum256(data)
|
|
||||||
filename := fmt.Sprintf("pages-hero-%d-%x", repo.ID, hash[:8])
|
|
||||||
|
|
||||||
config := getPagesLandingConfig(ctx)
|
|
||||||
|
|
||||||
// Delete old uploaded image if it exists
|
|
||||||
if config.Hero.UploadedImage != "" {
|
|
||||||
_ = storage.RepoAvatars.Delete(config.Hero.UploadedImage)
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := storage.SaveFrom(storage.RepoAvatars, filename, func(w io.Writer) error {
|
|
||||||
_, err := w.Write(data)
|
|
||||||
return err
|
|
||||||
}); err != nil {
|
|
||||||
ctx.ServerError("SaveFrom", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
config.Hero.UploadedImage = filename
|
|
||||||
if err := savePagesLandingConfig(ctx, config); err != nil {
|
|
||||||
ctx.ServerError("SavePagesConfig", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
ctx.Flash.Success(ctx.Tr("repo.settings.pages.hero_image_uploaded"))
|
|
||||||
ctx.Redirect(repo.Link() + "/settings/pages/hero")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// PagesHeroDeleteImage removes the uploaded hero image.
|
// PagesHeroDeleteImage removes the uploaded hero image.
|
||||||
func PagesHeroDeleteImage(ctx *context.Context) {
|
func PagesHeroDeleteImage(ctx *context.Context) {
|
||||||
repo := ctx.Repo.Repository
|
pagesDeleteImage(ctx, "/settings/pages/hero",
|
||||||
config := getPagesLandingConfig(ctx)
|
func(c *pages_module.LandingConfig) string { return c.Hero.UploadedImage },
|
||||||
|
func(c *pages_module.LandingConfig) { c.Hero.UploadedImage = "" },
|
||||||
if config.Hero.UploadedImage != "" {
|
"repo.settings.pages.hero_image_deleted")
|
||||||
_ = storage.RepoAvatars.Delete(config.Hero.UploadedImage)
|
|
||||||
config.Hero.UploadedImage = ""
|
|
||||||
if err := savePagesLandingConfig(ctx, config); err != nil {
|
|
||||||
ctx.ServerError("SavePagesConfig", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ctx.Flash.Success(ctx.Tr("repo.settings.pages.hero_image_deleted"))
|
|
||||||
ctx.Redirect(repo.Link() + "/settings/pages/hero")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func PagesContent(ctx *context.Context) {
|
func PagesContent(ctx *context.Context) {
|
||||||
|
|||||||
Reference in New Issue
Block a user