公开 /links 列表与申请/取消,导航页脚可配入口;后台品牌增删与通过/拒绝。 Co-authored-by: Cursor <cursoragent@cursor.com>
256 lines
7.0 KiB
Go
256 lines
7.0 KiB
Go
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})
|
||
}
|