feat: 发评限流与品牌 Logo/Favicon/OG 上传
发评挂 RateLimiter comment;设置页支持品牌图上传/清除,并写入 favicon 与 og:image。 Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -18,11 +18,19 @@ type Deps struct {
|
||||
}
|
||||
|
||||
type pageData struct {
|
||||
Title string
|
||||
Description string
|
||||
SiteName string
|
||||
Slogan string
|
||||
LogoMark string
|
||||
Title string
|
||||
Description string
|
||||
SiteName string
|
||||
Slogan string
|
||||
LogoMark string
|
||||
LogoURL string
|
||||
FaviconURL string
|
||||
OGImageURL string
|
||||
ShowFriendLinksFooter bool
|
||||
FooterPages []struct {
|
||||
Title string
|
||||
Slug string
|
||||
}
|
||||
CSRF string
|
||||
Error string
|
||||
Flash string
|
||||
|
||||
@@ -520,6 +520,90 @@ func (d Deps) AdminSettingsBrandPost(c *gin.Context) {
|
||||
ctx.Redirect("/admin/settings")
|
||||
}
|
||||
|
||||
// AdminSettingsBrandUploadPost 上传 Logo / Favicon / OG 图
|
||||
func (d Deps) AdminSettingsBrandUploadPost(c *gin.Context) {
|
||||
ctx := d.ctx(c)
|
||||
if !ctx.CheckCSRF() {
|
||||
d.renderAdminSettings(ctx, "无效请求,请重试")
|
||||
return
|
||||
}
|
||||
if d.Store == nil {
|
||||
d.renderAdminSettings(ctx, "上传存储未就绪")
|
||||
return
|
||||
}
|
||||
kind := strings.TrimSpace(c.PostForm("kind"))
|
||||
if kind != "logo" && kind != "favicon" && kind != "og_image" {
|
||||
d.renderAdminSettings(ctx, "请选择 Logo、Favicon 或 OG 图")
|
||||
return
|
||||
}
|
||||
file, err := c.FormFile("file")
|
||||
if err != nil {
|
||||
d.renderAdminSettings(ctx, "请选择图片文件")
|
||||
return
|
||||
}
|
||||
const maxBytes = 2 * 1024 * 1024
|
||||
if file.Size > maxBytes {
|
||||
d.renderAdminSettings(ctx, "图片不能超过 2MB")
|
||||
return
|
||||
}
|
||||
url, err := services.SaveUploadedImage(d.Store, file, services.UploadCategorySite, kind)
|
||||
if err != nil {
|
||||
d.renderAdminSettings(ctx, err.Error())
|
||||
return
|
||||
}
|
||||
prev := d.Settings.SiteBranding()
|
||||
switch kind {
|
||||
case "logo":
|
||||
_ = d.Settings.SetSiteLogo(url)
|
||||
d.Store.DeleteByURL(prev.Logo)
|
||||
ctx.SetFlash("Logo 已上传")
|
||||
case "favicon":
|
||||
_ = d.Settings.SetSiteFavicon(url)
|
||||
d.Store.DeleteByURL(prev.Favicon)
|
||||
ctx.SetFlash("Favicon 已上传")
|
||||
case "og_image":
|
||||
_ = d.Settings.SetSiteOGImage(url)
|
||||
d.Store.DeleteByURL(prev.OGImage)
|
||||
ctx.SetFlash("默认 OG 图已上传")
|
||||
}
|
||||
ctx.Redirect("/admin/settings")
|
||||
}
|
||||
|
||||
// AdminSettingsBrandClearPost 清除品牌图片
|
||||
func (d Deps) AdminSettingsBrandClearPost(c *gin.Context) {
|
||||
ctx := d.ctx(c)
|
||||
if !ctx.CheckCSRF() {
|
||||
d.renderAdminSettings(ctx, "无效请求,请重试")
|
||||
return
|
||||
}
|
||||
kind := strings.TrimSpace(c.PostForm("kind"))
|
||||
brand := d.Settings.SiteBranding()
|
||||
switch kind {
|
||||
case "logo":
|
||||
_ = d.Settings.SetSiteLogo("")
|
||||
if d.Store != nil {
|
||||
d.Store.DeleteByURL(brand.Logo)
|
||||
}
|
||||
ctx.SetFlash("已清除 Logo")
|
||||
case "favicon":
|
||||
_ = d.Settings.SetSiteFavicon("")
|
||||
if d.Store != nil {
|
||||
d.Store.DeleteByURL(brand.Favicon)
|
||||
}
|
||||
ctx.SetFlash("已清除 Favicon")
|
||||
case "og_image":
|
||||
_ = d.Settings.SetSiteOGImage("")
|
||||
if d.Store != nil {
|
||||
d.Store.DeleteByURL(brand.OGImage)
|
||||
}
|
||||
ctx.SetFlash("已清除 OG 图")
|
||||
default:
|
||||
d.renderAdminSettings(ctx, "无效的资源类型")
|
||||
return
|
||||
}
|
||||
ctx.Redirect("/admin/settings")
|
||||
}
|
||||
|
||||
// AdminSettingsLimitsPost 限流
|
||||
func (d Deps) AdminSettingsLimitsPost(c *gin.Context) {
|
||||
ctx := d.ctx(c)
|
||||
|
||||
@@ -108,6 +108,9 @@ type PageChrome struct {
|
||||
SiteName string
|
||||
Slogan string
|
||||
LogoMark string
|
||||
LogoURL string
|
||||
FaviconURL string
|
||||
OGImageURL string
|
||||
LoggedIn bool
|
||||
IsAdmin bool
|
||||
ViewerName string
|
||||
@@ -170,6 +173,9 @@ func (d Deps) chrome(ctx *webctx.Context, title, desc, inner string) PageChrome
|
||||
SiteName: brand.Name,
|
||||
Slogan: brand.Slogan,
|
||||
LogoMark: firstRuneOr(brand.LogoMark, "姜"),
|
||||
LogoURL: strings.TrimSpace(brand.Logo),
|
||||
FaviconURL: strings.TrimSpace(brand.Favicon),
|
||||
OGImageURL: strings.TrimSpace(brand.DefaultShareImage()),
|
||||
LoggedIn: ctx.IsSigned(),
|
||||
IsAdmin: ctx.IsAdmin(),
|
||||
ViewerName: name,
|
||||
|
||||
@@ -75,6 +75,8 @@ func Register(r *gin.Engine, deps Deps, authMW *auth.AuthMiddleware) {
|
||||
admin.POST("/comments/:id/reject", deps.AdminCommentReject)
|
||||
admin.GET("/settings", deps.AdminSettingsGet)
|
||||
admin.POST("/settings/brand", deps.AdminSettingsBrandPost)
|
||||
admin.POST("/settings/brand/upload", deps.AdminSettingsBrandUploadPost)
|
||||
admin.POST("/settings/brand/clear", deps.AdminSettingsBrandClearPost)
|
||||
admin.POST("/settings/limits", deps.AdminSettingsLimitsPost)
|
||||
admin.POST("/settings/filter-words", deps.AdminSettingsFilterWordsPost)
|
||||
admin.POST("/settings/mail", deps.AdminSettingsMailPost)
|
||||
|
||||
@@ -391,6 +391,11 @@ func (d Deps) PostComment(c *gin.Context) {
|
||||
}
|
||||
}
|
||||
isPrivate := c.PostForm("is_private") == "1" || c.PostForm("is_private") == "on"
|
||||
if d.Limiter != nil && !d.Limiter.Allow("comment", fmt.Sprintf("%d", ctx.UserID())) {
|
||||
ctx.SetFlash("操作过于频繁,请稍后再试")
|
||||
ctx.Redirect(fmt.Sprintf("/post/%d#comments", id))
|
||||
return
|
||||
}
|
||||
safe := "<p>" + html.EscapeString(content) + "</p>"
|
||||
cm, err := d.Comment.Create(services.CommentCreateInput{
|
||||
PostID: id, UserID: ctx.UserID(), Content: safe,
|
||||
|
||||
Reference in New Issue
Block a user