feat: SSR 友链页与最小 Admin 审核
公开 /links 列表与申请/取消,导航页脚可配入口;后台品牌增删与通过/拒绝。 Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -100,7 +100,8 @@ func Setup(cfg *config.Config) (*gin.Engine, error) {
|
||||
Board: boardSvc, Post: postSvc, Comment: commentSvc,
|
||||
Message: messageSvc, Filter: filter,
|
||||
Limiter: limiter, EmailCode: emailCodeSvc, Store: uploadStore,
|
||||
Points: services.NewPointsService(),
|
||||
Points: services.NewPointsService(),
|
||||
FriendLink: friendLinkApplySvc,
|
||||
}, authMW)
|
||||
|
||||
r.GET("/media/thumb/*filepath", h.ServeImageThumb)
|
||||
|
||||
209
routers/web/admin_friend_links.go
Normal file
209
routers/web/admin_friend_links.go
Normal file
@@ -0,0 +1,209 @@
|
||||
package web
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"git.iioio.com/freefire/jiang13-forum/models"
|
||||
"git.iioio.com/freefire/jiang13-forum/modules/webctx"
|
||||
"git.iioio.com/freefire/jiang13-forum/services"
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
type adminFriendLinkBrandRow struct {
|
||||
Name string
|
||||
URL string
|
||||
Logo string
|
||||
}
|
||||
|
||||
type adminFriendLinkApplyRow struct {
|
||||
ID uint
|
||||
UserID uint
|
||||
Username string
|
||||
Name string
|
||||
URL string
|
||||
Logo string
|
||||
Status string
|
||||
StatusLabel string
|
||||
OnHome bool
|
||||
Reciprocal string
|
||||
RecipOK bool
|
||||
RecipNote string
|
||||
CreatedAt string
|
||||
CanReview bool
|
||||
}
|
||||
|
||||
type adminFriendLinksData struct {
|
||||
AdminChrome
|
||||
BrandLinks []adminFriendLinkBrandRow
|
||||
Applies []adminFriendLinkApplyRow
|
||||
PendingCount int64
|
||||
NavShow bool
|
||||
FooterShow bool
|
||||
ReciprocalCheck bool
|
||||
BrandName string
|
||||
BrandURL string
|
||||
BrandLogo string
|
||||
}
|
||||
|
||||
// AdminFriendLinksGet 友链管理
|
||||
func (d Deps) AdminFriendLinksGet(c *gin.Context) {
|
||||
ctx := d.ctx(c)
|
||||
d.renderAdminFriendLinks(ctx, "")
|
||||
}
|
||||
|
||||
func (d Deps) renderAdminFriendLinks(ctx *webctx.Context, errMsg string) {
|
||||
chrome := d.adminChrome(ctx, "友链", "friend-links")
|
||||
chrome.Error = errMsg
|
||||
data := adminFriendLinksData{
|
||||
AdminChrome: chrome,
|
||||
NavShow: d.Settings.NavShowFriendLinks(),
|
||||
FooterShow: d.Settings.FooterShowFriendLinks(),
|
||||
ReciprocalCheck: d.Settings.FriendLinkReciprocalCheckEnabled(),
|
||||
}
|
||||
brand := d.Settings.SiteBranding()
|
||||
for _, l := range brand.FriendLinks {
|
||||
data.BrandLinks = append(data.BrandLinks, adminFriendLinkBrandRow{Name: l.Name, URL: l.URL, Logo: l.Logo})
|
||||
}
|
||||
if d.FriendLink != nil {
|
||||
data.PendingCount, _ = d.FriendLink.PendingCount()
|
||||
rows, _, _ := d.FriendLink.ListAdmin(services.FriendLinkApplyListQuery{Page: 1, Size: 50, Status: "all"})
|
||||
data.Applies = make([]adminFriendLinkApplyRow, 0, len(rows))
|
||||
for _, a := range rows {
|
||||
uname := ""
|
||||
if a.User.ID > 0 {
|
||||
uname = a.User.Username
|
||||
}
|
||||
data.Applies = append(data.Applies, adminFriendLinkApplyRow{
|
||||
ID: a.ID,
|
||||
UserID: a.UserID,
|
||||
Username: uname,
|
||||
Name: a.Name,
|
||||
URL: a.URL,
|
||||
Logo: a.Logo,
|
||||
Status: a.Status,
|
||||
StatusLabel: friendLinkApplyStatusLabel(a.Status),
|
||||
OnHome: a.LinkOnHomepage,
|
||||
Reciprocal: a.ReciprocalPageURL,
|
||||
RecipOK: a.ReciprocalVerified,
|
||||
RecipNote: a.ReciprocalCheckNote,
|
||||
CreatedAt: a.CreatedAt.Format("2006-01-02 15:04"),
|
||||
CanReview: a.Status == models.FriendLinkApplyStatusPending,
|
||||
})
|
||||
}
|
||||
}
|
||||
ctx.HTML(http.StatusOK, "admin/friend_links", data)
|
||||
}
|
||||
|
||||
// AdminFriendLinksSettingsPost nav/footer/回链开关
|
||||
func (d Deps) AdminFriendLinksSettingsPost(c *gin.Context) {
|
||||
ctx := d.ctx(c)
|
||||
if !ctx.CheckCSRF() {
|
||||
d.renderAdminFriendLinks(ctx, "无效请求,请重试")
|
||||
return
|
||||
}
|
||||
_ = d.Settings.SetNavShowFriendLinks(c.PostForm("nav_show") == "1" || c.PostForm("nav_show") == "on")
|
||||
_ = d.Settings.SetFooterShowFriendLinks(c.PostForm("footer_show") == "1" || c.PostForm("footer_show") == "on")
|
||||
_ = d.Settings.SetFriendLinkReciprocalCheckEnabled(c.PostForm("reciprocal_check") == "1" || c.PostForm("reciprocal_check") == "on")
|
||||
ctx.SetFlash("友链入口设置已保存")
|
||||
ctx.Redirect("/admin/friend-links")
|
||||
}
|
||||
|
||||
// AdminFriendLinksBrandAddPost 添加品牌友链
|
||||
func (d Deps) AdminFriendLinksBrandAddPost(c *gin.Context) {
|
||||
ctx := d.ctx(c)
|
||||
name := strings.TrimSpace(c.PostForm("name"))
|
||||
url := strings.TrimSpace(c.PostForm("url"))
|
||||
logo := strings.TrimSpace(c.PostForm("logo"))
|
||||
if !ctx.CheckCSRF() {
|
||||
d.renderAdminFriendLinks(ctx, "无效请求,请重试")
|
||||
return
|
||||
}
|
||||
brand := d.Settings.SiteBranding()
|
||||
next := append([]services.FriendLink{}, brand.FriendLinks...)
|
||||
next = append(next, services.FriendLink{Name: name, URL: url, Logo: logo})
|
||||
if err := d.Settings.UpdateSiteBranding(services.SiteBranding{
|
||||
Name: brand.Name, Slogan: brand.Slogan, Description: brand.Description,
|
||||
Keywords: brand.Keywords, LogoMark: brand.LogoMark, Logo: brand.Logo,
|
||||
Favicon: brand.Favicon, OGImage: brand.OGImage,
|
||||
ICPBeian: brand.ICPBeian, ICPBeianURL: brand.ICPBeianURL,
|
||||
FriendLinks: next,
|
||||
}); err != nil {
|
||||
d.renderAdminFriendLinks(ctx, err.Error())
|
||||
return
|
||||
}
|
||||
ctx.SetFlash("已添加品牌友链")
|
||||
ctx.Redirect("/admin/friend-links")
|
||||
}
|
||||
|
||||
// AdminFriendLinksBrandDeletePost 删除品牌友链(按 URL)
|
||||
func (d Deps) AdminFriendLinksBrandDeletePost(c *gin.Context) {
|
||||
ctx := d.ctx(c)
|
||||
if !ctx.CheckCSRF() {
|
||||
d.renderAdminFriendLinks(ctx, "无效请求,请重试")
|
||||
return
|
||||
}
|
||||
target := strings.TrimSpace(c.PostForm("url"))
|
||||
brand := d.Settings.SiteBranding()
|
||||
next := make([]services.FriendLink, 0, len(brand.FriendLinks))
|
||||
for _, l := range brand.FriendLinks {
|
||||
if l.URL == target {
|
||||
continue
|
||||
}
|
||||
next = append(next, l)
|
||||
}
|
||||
if err := d.Settings.UpdateSiteBranding(services.SiteBranding{
|
||||
Name: brand.Name, Slogan: brand.Slogan, Description: brand.Description,
|
||||
Keywords: brand.Keywords, LogoMark: brand.LogoMark, Logo: brand.Logo,
|
||||
Favicon: brand.Favicon, OGImage: brand.OGImage,
|
||||
ICPBeian: brand.ICPBeian, ICPBeianURL: brand.ICPBeianURL,
|
||||
FriendLinks: next,
|
||||
}); err != nil {
|
||||
d.renderAdminFriendLinks(ctx, err.Error())
|
||||
return
|
||||
}
|
||||
ctx.SetFlash("已删除品牌友链")
|
||||
ctx.Redirect("/admin/friend-links")
|
||||
}
|
||||
|
||||
// AdminFriendLinkApprovePost 通过申请
|
||||
func (d Deps) AdminFriendLinkApprovePost(c *gin.Context) {
|
||||
ctx := d.ctx(c)
|
||||
if !ctx.CheckCSRF() {
|
||||
d.renderAdminFriendLinks(ctx, "无效请求,请重试")
|
||||
return
|
||||
}
|
||||
if d.FriendLink == nil {
|
||||
d.renderAdminFriendLinks(ctx, "友链服务未就绪")
|
||||
return
|
||||
}
|
||||
id64, _ := strconv.ParseUint(c.Param("id"), 10, 64)
|
||||
if _, err := d.FriendLink.Approve(uint(id64)); err != nil {
|
||||
d.renderAdminFriendLinks(ctx, err.Error())
|
||||
return
|
||||
}
|
||||
ctx.SetFlash("已通过友链申请")
|
||||
ctx.Redirect("/admin/friend-links")
|
||||
}
|
||||
|
||||
// AdminFriendLinkRejectPost 拒绝申请
|
||||
func (d Deps) AdminFriendLinkRejectPost(c *gin.Context) {
|
||||
ctx := d.ctx(c)
|
||||
if !ctx.CheckCSRF() {
|
||||
d.renderAdminFriendLinks(ctx, "无效请求,请重试")
|
||||
return
|
||||
}
|
||||
if d.FriendLink == nil {
|
||||
d.renderAdminFriendLinks(ctx, "友链服务未就绪")
|
||||
return
|
||||
}
|
||||
id64, _ := strconv.ParseUint(c.Param("id"), 10, 64)
|
||||
note := strings.TrimSpace(c.PostForm("note"))
|
||||
if _, err := d.FriendLink.Reject(uint(id64), note); err != nil {
|
||||
d.renderAdminFriendLinks(ctx, err.Error())
|
||||
return
|
||||
}
|
||||
ctx.SetFlash("已拒绝友链申请")
|
||||
ctx.Redirect("/admin/friend-links")
|
||||
}
|
||||
@@ -22,8 +22,9 @@ type Deps struct {
|
||||
Filter *services.SensitiveFilter
|
||||
Limiter *services.RateLimiter
|
||||
EmailCode *services.EmailCodeService
|
||||
Store *services.UploadStore
|
||||
Points *services.PointsService
|
||||
Store *services.UploadStore
|
||||
Points *services.PointsService
|
||||
FriendLink *services.FriendLinkApplyService
|
||||
}
|
||||
|
||||
// BoardView 侧栏
|
||||
@@ -48,8 +49,10 @@ type PageChrome struct {
|
||||
CSRF string
|
||||
Flash string
|
||||
Error string
|
||||
UnreadCount int64 // 登录用户未读私信/通知总数;未登录为 0
|
||||
ViewerPoints int // 登录用户当前积分;未登录为 0
|
||||
UnreadCount int64 // 登录用户未读私信/通知总数;未登录为 0
|
||||
ViewerPoints int // 登录用户当前积分;未登录为 0
|
||||
ShowFriendLinksNav bool
|
||||
ShowFriendLinksFooter bool
|
||||
}
|
||||
|
||||
func (d Deps) ctx(c *gin.Context) *webctx.Context {
|
||||
@@ -85,20 +88,22 @@ func (d Deps) chrome(ctx *webctx.Context, title, desc, inner string) PageChrome
|
||||
viewerPoints = ctx.Doer.Points
|
||||
}
|
||||
return PageChrome{
|
||||
Title: title,
|
||||
Description: desc,
|
||||
SiteName: brand.Name,
|
||||
Slogan: brand.Slogan,
|
||||
LogoMark: firstRuneOr(brand.LogoMark, "姜"),
|
||||
LoggedIn: ctx.IsSigned(),
|
||||
IsAdmin: ctx.IsAdmin(),
|
||||
ViewerName: name,
|
||||
Boards: bv,
|
||||
Inner: inner,
|
||||
CSRF: ctx.EnsureCSRF(),
|
||||
Flash: ctx.TakeFlash(),
|
||||
UnreadCount: unread,
|
||||
ViewerPoints: viewerPoints,
|
||||
Title: title,
|
||||
Description: desc,
|
||||
SiteName: brand.Name,
|
||||
Slogan: brand.Slogan,
|
||||
LogoMark: firstRuneOr(brand.LogoMark, "姜"),
|
||||
LoggedIn: ctx.IsSigned(),
|
||||
IsAdmin: ctx.IsAdmin(),
|
||||
ViewerName: name,
|
||||
Boards: bv,
|
||||
Inner: inner,
|
||||
CSRF: ctx.EnsureCSRF(),
|
||||
Flash: ctx.TakeFlash(),
|
||||
UnreadCount: unread,
|
||||
ViewerPoints: viewerPoints,
|
||||
ShowFriendLinksNav: d.Settings.NavShowFriendLinks(),
|
||||
ShowFriendLinksFooter: d.Settings.FooterShowFriendLinks(),
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -51,6 +51,12 @@ func Register(r *gin.Engine, deps Deps, authMW *auth.AuthMiddleware) {
|
||||
admin.POST("/settings/brand", deps.AdminSettingsBrandPost)
|
||||
admin.POST("/settings/limits", deps.AdminSettingsLimitsPost)
|
||||
admin.POST("/settings/filter-words", deps.AdminSettingsFilterWordsPost)
|
||||
admin.GET("/friend-links", deps.AdminFriendLinksGet)
|
||||
admin.POST("/friend-links/settings", deps.AdminFriendLinksSettingsPost)
|
||||
admin.POST("/friend-links/brand", deps.AdminFriendLinksBrandAddPost)
|
||||
admin.POST("/friend-links/brand/delete", deps.AdminFriendLinksBrandDeletePost)
|
||||
admin.POST("/friend-links/applies/:id/approve", deps.AdminFriendLinkApprovePost)
|
||||
admin.POST("/friend-links/applies/:id/reject", deps.AdminFriendLinkRejectPost)
|
||||
}
|
||||
|
||||
g.GET("/user/:id", deps.UserPublic)
|
||||
@@ -66,8 +72,11 @@ func Register(r *gin.Engine, deps Deps, authMW *auth.AuthMiddleware) {
|
||||
g.GET("/messages/with/:peerId", authMW.RequireAuth(), deps.MessagesThread)
|
||||
g.POST("/messages/with/:peerId", authMW.RequireAuth(), deps.MessagesSend)
|
||||
g.POST("/messages/read-all", authMW.RequireAuth(), deps.MessagesReadAll)
|
||||
g.GET("/links", deps.LinksGet)
|
||||
g.POST("/links/apply", authMW.RequireAuth(), deps.LinksApplyPost)
|
||||
g.POST("/links/apply/:id/cancel", authMW.RequireAuth(), deps.LinksApplyCancelPost)
|
||||
g.POST("/links/logo", authMW.RequireAuth(), deps.LinksLogoUpload)
|
||||
g.GET("/projects", deps.PendingPage)
|
||||
g.GET("/links", deps.PendingPage)
|
||||
g.GET("/boards", deps.PendingPage)
|
||||
}
|
||||
|
||||
|
||||
255
routers/web/links.go
Normal file
255
routers/web/links.go
Normal file
@@ -0,0 +1,255 @@
|
||||
package web
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"git.iioio.com/freefire/jiang13-forum/models"
|
||||
"git.iioio.com/freefire/jiang13-forum/modules/webctx"
|
||||
"git.iioio.com/freefire/jiang13-forum/services"
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
type linksLinkView struct {
|
||||
Name string
|
||||
URL string
|
||||
Logo string
|
||||
}
|
||||
|
||||
type linksApplyView struct {
|
||||
ID uint
|
||||
Name string
|
||||
URL string
|
||||
Logo string
|
||||
Status string
|
||||
StatusLabel string
|
||||
ReviewNote string
|
||||
CreatedAt string
|
||||
CanCancel bool
|
||||
}
|
||||
|
||||
type linksPageData struct {
|
||||
PageChrome
|
||||
Links []linksLinkView
|
||||
MyApplies []linksApplyView
|
||||
FormName string
|
||||
FormURL string
|
||||
FormLogo string
|
||||
FormRecip string
|
||||
FormOnHome bool
|
||||
AvatarMaxMB int
|
||||
}
|
||||
|
||||
func friendLinkApplyStatusLabel(status string) string {
|
||||
switch status {
|
||||
case models.FriendLinkApplyStatusPending:
|
||||
return "待审核"
|
||||
case models.FriendLinkApplyStatusApproved:
|
||||
return "已通过"
|
||||
case models.FriendLinkApplyStatusRejected:
|
||||
return "已拒绝"
|
||||
default:
|
||||
return status
|
||||
}
|
||||
}
|
||||
|
||||
func (d Deps) publicBaseURL(c *gin.Context) string {
|
||||
proto := c.GetHeader("X-Forwarded-Proto")
|
||||
if proto == "" {
|
||||
if c.Request.TLS != nil {
|
||||
proto = "https"
|
||||
} else {
|
||||
proto = "http"
|
||||
}
|
||||
}
|
||||
host := c.GetHeader("X-Forwarded-Host")
|
||||
if host == "" {
|
||||
host = c.Request.Host
|
||||
}
|
||||
origin := ""
|
||||
if host != "" {
|
||||
origin = proto + "://" + host
|
||||
}
|
||||
return d.Settings.SitePublicBaseURL(origin)
|
||||
}
|
||||
|
||||
// LinksGet 友链公开页
|
||||
func (d Deps) LinksGet(c *gin.Context) {
|
||||
ctx := d.ctx(c)
|
||||
d.renderLinks(ctx, c, "")
|
||||
}
|
||||
|
||||
func (d Deps) renderLinks(ctx *webctx.Context, c *gin.Context, errMsg string) {
|
||||
brand := d.Settings.SiteBranding()
|
||||
chrome := d.chrome(ctx, "友情链接 · "+brand.Name, "", "")
|
||||
chrome.Error = errMsg
|
||||
data := linksPageData{
|
||||
PageChrome: chrome,
|
||||
FormOnHome: true,
|
||||
AvatarMaxMB: d.Settings.AvatarMaxMB(),
|
||||
}
|
||||
for _, l := range brand.FriendLinks {
|
||||
data.Links = append(data.Links, linksLinkView{Name: l.Name, URL: l.URL, Logo: l.Logo})
|
||||
}
|
||||
if ctx.IsSigned() && d.FriendLink != nil {
|
||||
rows, _ := d.FriendLink.ListMine(ctx.UserID())
|
||||
data.MyApplies = make([]linksApplyView, 0, len(rows))
|
||||
for _, a := range rows {
|
||||
data.MyApplies = append(data.MyApplies, linksApplyView{
|
||||
ID: a.ID,
|
||||
Name: a.Name,
|
||||
URL: a.URL,
|
||||
Logo: a.Logo,
|
||||
Status: a.Status,
|
||||
StatusLabel: friendLinkApplyStatusLabel(a.Status),
|
||||
ReviewNote: a.ReviewNote,
|
||||
CreatedAt: a.CreatedAt.Format("2006-01-02 15:04"),
|
||||
CanCancel: a.Status == models.FriendLinkApplyStatusPending,
|
||||
})
|
||||
}
|
||||
}
|
||||
ctx.HTML(http.StatusOK, "links/list", data)
|
||||
}
|
||||
|
||||
func (d Deps) renderLinksForm(ctx *webctx.Context, c *gin.Context, errMsg string, form linksPageData) {
|
||||
brand := d.Settings.SiteBranding()
|
||||
chrome := d.chrome(ctx, "友情链接 · "+brand.Name, "", "")
|
||||
chrome.Error = errMsg
|
||||
form.PageChrome = chrome
|
||||
form.AvatarMaxMB = d.Settings.AvatarMaxMB()
|
||||
for _, l := range brand.FriendLinks {
|
||||
form.Links = append(form.Links, linksLinkView{Name: l.Name, URL: l.URL, Logo: l.Logo})
|
||||
}
|
||||
if ctx.IsSigned() && d.FriendLink != nil {
|
||||
rows, _ := d.FriendLink.ListMine(ctx.UserID())
|
||||
form.MyApplies = make([]linksApplyView, 0, len(rows))
|
||||
for _, a := range rows {
|
||||
form.MyApplies = append(form.MyApplies, linksApplyView{
|
||||
ID: a.ID,
|
||||
Name: a.Name,
|
||||
URL: a.URL,
|
||||
Logo: a.Logo,
|
||||
Status: a.Status,
|
||||
StatusLabel: friendLinkApplyStatusLabel(a.Status),
|
||||
ReviewNote: a.ReviewNote,
|
||||
CreatedAt: a.CreatedAt.Format("2006-01-02 15:04"),
|
||||
CanCancel: a.Status == models.FriendLinkApplyStatusPending,
|
||||
})
|
||||
}
|
||||
}
|
||||
ctx.HTML(http.StatusOK, "links/list", form)
|
||||
}
|
||||
|
||||
// LinksApplyPost 提交友链申请
|
||||
func (d Deps) LinksApplyPost(c *gin.Context) {
|
||||
ctx := d.ctx(c)
|
||||
form := linksPageData{
|
||||
FormName: strings.TrimSpace(c.PostForm("name")),
|
||||
FormURL: strings.TrimSpace(c.PostForm("url")),
|
||||
FormLogo: strings.TrimSpace(c.PostForm("logo")),
|
||||
FormRecip: strings.TrimSpace(c.PostForm("reciprocal_page_url")),
|
||||
FormOnHome: c.PostForm("link_on_homepage") == "1" || c.PostForm("link_on_homepage") == "on",
|
||||
}
|
||||
if !ctx.CheckCSRF() {
|
||||
d.renderLinksForm(ctx, c, "无效请求,请重试", form)
|
||||
return
|
||||
}
|
||||
if d.FriendLink == nil {
|
||||
d.renderLinksForm(ctx, c, "友链服务未就绪", form)
|
||||
return
|
||||
}
|
||||
if d.Limiter != nil && !d.Limiter.Allow("friend_link", fmt.Sprintf("%d", ctx.UserID())) {
|
||||
d.renderLinksForm(ctx, c, "申请过于频繁,请稍后再试", form)
|
||||
return
|
||||
}
|
||||
logo := form.FormLogo
|
||||
if file, err := c.FormFile("logo_file"); err == nil && file != nil {
|
||||
if d.Store == nil {
|
||||
d.renderLinksForm(ctx, c, "上传存储未就绪", form)
|
||||
return
|
||||
}
|
||||
url, err := services.SaveUploadedImage(d.Store, file, services.UploadCategorySite, fmt.Sprintf("fl_%d", ctx.UserID()))
|
||||
if err != nil {
|
||||
d.renderLinksForm(ctx, c, err.Error(), form)
|
||||
return
|
||||
}
|
||||
logo = url
|
||||
form.FormLogo = url
|
||||
}
|
||||
_, err := d.FriendLink.Create(services.FriendLinkApplyInput{
|
||||
UserID: ctx.UserID(),
|
||||
Name: form.FormName,
|
||||
URL: form.FormURL,
|
||||
Logo: logo,
|
||||
LinkOnHomepage: form.FormOnHome,
|
||||
ReciprocalPageURL: form.FormRecip,
|
||||
OurSiteURL: d.publicBaseURL(c),
|
||||
})
|
||||
if err != nil {
|
||||
d.renderLinksForm(ctx, c, err.Error(), form)
|
||||
return
|
||||
}
|
||||
ctx.SetFlash("友链申请已提交,请等待审核")
|
||||
ctx.Redirect("/links")
|
||||
}
|
||||
|
||||
// LinksApplyCancelPost 取消待审申请
|
||||
func (d Deps) LinksApplyCancelPost(c *gin.Context) {
|
||||
ctx := d.ctx(c)
|
||||
if !ctx.CheckCSRF() {
|
||||
d.renderLinks(ctx, c, "无效请求,请重试")
|
||||
return
|
||||
}
|
||||
if d.FriendLink == nil {
|
||||
d.renderLinks(ctx, c, "友链服务未就绪")
|
||||
return
|
||||
}
|
||||
id64, _ := strconv.ParseUint(c.Param("id"), 10, 64)
|
||||
if err := d.FriendLink.Cancel(ctx.UserID(), uint(id64)); err != nil {
|
||||
if errors.Is(err, services.ErrFriendLinkApplyNotFound) || errors.Is(err, services.ErrFriendLinkApplyHandled) {
|
||||
ctx.SetFlash(err.Error())
|
||||
ctx.Redirect("/links")
|
||||
return
|
||||
}
|
||||
d.renderLinks(ctx, c, err.Error())
|
||||
return
|
||||
}
|
||||
ctx.SetFlash("已取消申请")
|
||||
ctx.Redirect("/links")
|
||||
}
|
||||
|
||||
// LinksLogoUpload Logo 上传(JSON,供表单渐进增强)
|
||||
func (d Deps) LinksLogoUpload(c *gin.Context) {
|
||||
ctx := d.ctx(c)
|
||||
if !ctx.IsSigned() {
|
||||
c.JSON(http.StatusUnauthorized, gin.H{"error": "请先登录"})
|
||||
return
|
||||
}
|
||||
if !ctx.CheckCSRF() {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "无效请求"})
|
||||
return
|
||||
}
|
||||
if d.Store == nil {
|
||||
c.JSON(http.StatusServiceUnavailable, gin.H{"error": "上传不可用"})
|
||||
return
|
||||
}
|
||||
file, err := c.FormFile("logo")
|
||||
if err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "请选择 LOGO 图片"})
|
||||
return
|
||||
}
|
||||
maxBytes := int64(d.Settings.AvatarMaxMB()) * 1024 * 1024
|
||||
if file.Size > maxBytes {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "图片文件过大"})
|
||||
return
|
||||
}
|
||||
url, err := services.SaveUploadedImage(d.Store, file, services.UploadCategorySite, fmt.Sprintf("fl_%d", ctx.UserID()))
|
||||
if err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
c.JSON(http.StatusOK, gin.H{"message": "LOGO 已上传", "url": url})
|
||||
}
|
||||
Reference in New Issue
Block a user