增加 @提及、搜索筛选与找回密码,并将右栏热门改为正在聊。

补齐评论提及补全与通知、按作者/板块/仅标题搜索,以及邮箱验证码重置密码;同时修复 Go 提及正则并优化侧栏悬停样式。

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-08-07 07:39:33 +08:00
parent 10185178e2
commit 94f6a9666b
26 changed files with 1315 additions and 153 deletions

View File

@@ -364,6 +364,7 @@ func (h *Handlers) APIAdminApproveComment(c *gin.Context) {
if comment, err := h.Comment.GetByID(uint(id)); err == nil {
comment.Status = model.ContentStatusPublished
h.Notify.AsyncNotifyCommentPublished(comment)
h.Notify.AsyncNotifyCommentMentions(comment)
}
}
c.JSON(http.StatusOK, gin.H{"message": "评论已通过审核", "status": model.ContentStatusPublished})
@@ -933,6 +934,8 @@ func (h *Handlers) APIPosts(c *gin.Context) {
userID, _ := strconv.ParseUint(c.Query("user_id"), 10, 64)
keyword := c.Query("keyword")
tag := strings.TrimSpace(c.Query("tag"))
author := strings.TrimSpace(c.Query("author"))
titleOnly := c.Query("title_only") == "1" || strings.EqualFold(c.Query("title_only"), "true")
q := service.PostListQuery{
BoardID: uint(boardID),
@@ -941,6 +944,8 @@ func (h *Handlers) APIPosts(c *gin.Context) {
Size: size,
Keyword: keyword,
Tag: tag,
Author: author,
TitleOnly: titleOnly,
Sort: c.DefaultQuery("sort", "latest"),
ViewerID: h.currentUserID(c),
ViewerIsAdmin: h.isAdmin(c),
@@ -1063,7 +1068,7 @@ func (h *Handlers) APIPostComments(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{"comments": comments, "total": len(comments)})
}
// APIHotPosts 热门 TOP
// APIHotPosts 近期活跃讨论(近 7 日有回复)
func (h *Handlers) APIHotPosts(c *gin.Context) {
items, err := h.Post.HotPosts(10)
if err != nil {

View File

@@ -155,6 +155,77 @@ func (h *Handlers) APISendRegisterEmailCode(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{"message": "验证码已发送"})
}
// APISendResetEmailCode 发送重置密码验证码
func (h *Handlers) APISendResetEmailCode(c *gin.Context) {
var req struct {
Email string `json:"email" form:"email" binding:"required"`
}
if err := c.ShouldBind(&req); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": "参数错误"})
return
}
if !h.Settings.MailReady() {
c.JSON(http.StatusBadRequest, gin.H{"error": service.ErrMailNotConfigured.Error()})
return
}
if err := h.EmailCode.SendResetCode(req.Email); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
c.JSON(http.StatusOK, gin.H{"message": "若该邮箱已注册,验证码将发送到邮箱"})
}
// APIResetPassword 邮箱验证码重置密码
func (h *Handlers) APIResetPassword(c *gin.Context) {
var req struct {
Email string `json:"email" form:"email" binding:"required"`
EmailCode string `json:"email_code" form:"email_code" binding:"required"`
NewPassword string `json:"new_password" form:"new_password" binding:"required"`
}
if err := c.ShouldBind(&req); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": "参数错误"})
return
}
if !h.Settings.MailReady() {
c.JSON(http.StatusBadRequest, gin.H{"error": service.ErrMailNotConfigured.Error()})
return
}
if !h.EmailCode.VerifyPurpose(service.EmailCodePurposeReset, req.Email, req.EmailCode) {
c.JSON(http.StatusBadRequest, gin.H{"error": service.ErrEmailCodeInvalid.Error()})
return
}
if err := h.User.ResetPasswordByEmail(req.Email, req.NewPassword); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
c.JSON(http.StatusOK, gin.H{"message": "密码已重置,请使用新密码登录"})
}
// APISearchUsers 用户搜索(@补全)
func (h *Handlers) APISearchUsers(c *gin.Context) {
q := strings.TrimSpace(c.Query("q"))
if q == "" {
c.JSON(http.StatusOK, gin.H{"users": []any{}})
return
}
limit, _ := strconv.Atoi(c.DefaultQuery("limit", "8"))
users, err := h.User.SearchUsersBrief(q, limit)
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
}
out := make([]gin.H, 0, len(users))
for _, u := range users {
out = append(out, gin.H{
"id": u.ID,
"username": u.Username,
"nickname": u.Nickname,
"avatar": u.Avatar,
})
}
c.JSON(http.StatusOK, gin.H{"users": out})
}
func (h *Handlers) APIRegister(c *gin.Context) {
var req struct {
Username string `json:"username" form:"username" binding:"required"`
@@ -468,6 +539,7 @@ func (h *Handlers) APICreateComment(c *gin.Context) {
switch comment.Status {
case model.ContentStatusPublished:
h.Notify.AsyncNotifyCommentPublished(comment)
h.Notify.AsyncNotifyCommentMentions(comment)
case model.ContentStatusPending:
msg = "评论已提交,审核通过后公开显示"
h.Notify.AsyncNotifyPendingComment(comment)