feat: add user announcement endpoints
Expose active announcements to authenticated users, track read state, and feed active database announcements into /api/status with legacy fallback.
This commit is contained in:
parent
d9d79638c0
commit
eec9f4dcae
117
controller/announcement_user.go
Normal file
117
controller/announcement_user.go
Normal file
@ -0,0 +1,117 @@
|
||||
package controller
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/QuantumNous/new-api/common"
|
||||
"github.com/QuantumNous/new-api/model"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
type userAnnouncementResponse struct {
|
||||
model.Announcement
|
||||
IsRead bool `json:"is_read"`
|
||||
}
|
||||
|
||||
func GetUserAnnouncements(c *gin.Context) {
|
||||
userId := c.GetInt("id")
|
||||
unreadOnly := c.Query("unread") == "true"
|
||||
|
||||
var announcements []model.Announcement
|
||||
var err error
|
||||
if unreadOnly {
|
||||
announcements, err = model.GetUnreadAnnouncements(userId)
|
||||
} else {
|
||||
announcements, err = model.GetActiveAnnouncements()
|
||||
}
|
||||
if err != nil {
|
||||
common.ApiError(c, err)
|
||||
return
|
||||
}
|
||||
|
||||
result := make([]userAnnouncementResponse, 0, len(announcements))
|
||||
for _, announcement := range announcements {
|
||||
isRead := false
|
||||
if !unreadOnly {
|
||||
isRead, err = model.IsAnnouncementRead(announcement.Id, userId)
|
||||
if err != nil {
|
||||
common.ApiError(c, err)
|
||||
return
|
||||
}
|
||||
}
|
||||
result = append(result, userAnnouncementResponse{
|
||||
Announcement: announcement,
|
||||
IsRead: isRead,
|
||||
})
|
||||
}
|
||||
|
||||
common.ApiSuccess(c, result)
|
||||
}
|
||||
|
||||
func MarkAnnouncementRead(c *gin.Context) {
|
||||
announcementId, err := strconv.Atoi(c.Param("id"))
|
||||
if err != nil || announcementId <= 0 {
|
||||
common.ApiErrorMsg(c, "无效的公告 ID")
|
||||
return
|
||||
}
|
||||
|
||||
announcement, err := model.GetAnnouncementByID(announcementId)
|
||||
if err != nil {
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
common.ApiErrorMsg(c, "公告不存在或不可见")
|
||||
return
|
||||
}
|
||||
common.ApiError(c, err)
|
||||
return
|
||||
}
|
||||
if !isAnnouncementVisible(announcement) {
|
||||
common.ApiErrorMsg(c, "公告不存在或不可见")
|
||||
return
|
||||
}
|
||||
|
||||
userId := c.GetInt("id")
|
||||
isRead, err := model.IsAnnouncementRead(announcementId, userId)
|
||||
if err != nil {
|
||||
common.ApiError(c, err)
|
||||
return
|
||||
}
|
||||
if isRead {
|
||||
common.ApiSuccess(c, nil)
|
||||
return
|
||||
}
|
||||
|
||||
read := &model.AnnouncementRead{
|
||||
AnnouncementId: announcementId,
|
||||
UserId: userId,
|
||||
ReadAt: time.Now(),
|
||||
}
|
||||
if err := read.Insert(); err != nil {
|
||||
isRead, checkErr := model.IsAnnouncementRead(announcementId, userId)
|
||||
if checkErr == nil && isRead {
|
||||
common.ApiSuccess(c, nil)
|
||||
return
|
||||
}
|
||||
common.ApiError(c, err)
|
||||
return
|
||||
}
|
||||
|
||||
common.ApiSuccess(c, nil)
|
||||
}
|
||||
|
||||
func isAnnouncementVisible(announcement *model.Announcement) bool {
|
||||
if announcement.Status != announcementStatusActive {
|
||||
return false
|
||||
}
|
||||
now := time.Now()
|
||||
if announcement.StartsAt != nil && announcement.StartsAt.After(now) {
|
||||
return false
|
||||
}
|
||||
if announcement.EndsAt != nil && announcement.EndsAt.Before(now) {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
@ -5,6 +5,7 @@ import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/QuantumNous/new-api/common"
|
||||
"github.com/QuantumNous/new-api/constant"
|
||||
@ -43,10 +44,23 @@ func GetStatus(c *gin.Context) {
|
||||
|
||||
cs := console_setting.GetConsoleSetting()
|
||||
common.OptionMapRWMutex.RLock()
|
||||
defer common.OptionMapRWMutex.RUnlock()
|
||||
|
||||
passkeySetting := system_setting.GetPasskeySettings()
|
||||
legalSetting := system_setting.GetLegalSettings()
|
||||
apiInfoEnabled := cs.ApiInfoEnabled
|
||||
announcementsEnabled := cs.AnnouncementsEnabled
|
||||
faqEnabled := cs.FAQEnabled
|
||||
var apiInfo []map[string]interface{}
|
||||
if apiInfoEnabled {
|
||||
apiInfo = console_setting.GetApiInfo()
|
||||
}
|
||||
legacyAnnouncements := console_setting.GetAnnouncements()
|
||||
var faq []map[string]interface{}
|
||||
if faqEnabled {
|
||||
faq = console_setting.GetFAQ()
|
||||
}
|
||||
headerNavModules := common.OptionMap["HeaderNavModules"]
|
||||
sidebarModulesAdmin := common.OptionMap["SidebarModulesAdmin"]
|
||||
|
||||
data := gin.H{
|
||||
"version": common.Version,
|
||||
@ -94,14 +108,14 @@ func GetStatus(c *gin.Context) {
|
||||
"stripe_unit_price": setting.StripeUnitPrice,
|
||||
|
||||
// 面板启用开关
|
||||
"api_info_enabled": cs.ApiInfoEnabled,
|
||||
"api_info_enabled": apiInfoEnabled,
|
||||
"uptime_kuma_enabled": cs.UptimeKumaEnabled,
|
||||
"announcements_enabled": cs.AnnouncementsEnabled,
|
||||
"faq_enabled": cs.FAQEnabled,
|
||||
"announcements_enabled": announcementsEnabled,
|
||||
"faq_enabled": faqEnabled,
|
||||
|
||||
// 模块管理配置
|
||||
"HeaderNavModules": common.OptionMap["HeaderNavModules"],
|
||||
"SidebarModulesAdmin": common.OptionMap["SidebarModulesAdmin"],
|
||||
"HeaderNavModules": headerNavModules,
|
||||
"SidebarModulesAdmin": sidebarModulesAdmin,
|
||||
|
||||
"oidc_enabled": system_setting.GetOIDCSettings().Enabled,
|
||||
"oidc_client_id": system_setting.GetOIDCSettings().ClientId,
|
||||
@ -118,16 +132,17 @@ func GetStatus(c *gin.Context) {
|
||||
"privacy_policy_enabled": legalSetting.PrivacyPolicy != "",
|
||||
"checkin_enabled": operation_setting.GetCheckinSetting().Enabled,
|
||||
}
|
||||
common.OptionMapRWMutex.RUnlock()
|
||||
|
||||
// 根据启用状态注入可选内容
|
||||
if cs.ApiInfoEnabled {
|
||||
data["api_info"] = console_setting.GetApiInfo()
|
||||
if apiInfoEnabled {
|
||||
data["api_info"] = apiInfo
|
||||
}
|
||||
if cs.AnnouncementsEnabled {
|
||||
data["announcements"] = console_setting.GetAnnouncements()
|
||||
if announcementsEnabled {
|
||||
data["announcements"] = getStatusAnnouncements(legacyAnnouncements)
|
||||
}
|
||||
if cs.FAQEnabled {
|
||||
data["faq"] = console_setting.GetFAQ()
|
||||
if faqEnabled {
|
||||
data["faq"] = faq
|
||||
}
|
||||
|
||||
// Add enabled custom OAuth providers
|
||||
@ -166,6 +181,45 @@ func GetStatus(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
func getStatusAnnouncements(fallback []map[string]interface{}) []map[string]interface{} {
|
||||
announcements, err := model.GetActiveAnnouncements()
|
||||
if err == nil && len(announcements) > 0 {
|
||||
result := make([]map[string]interface{}, 0, len(announcements))
|
||||
for _, announcement := range announcements {
|
||||
result = append(result, buildStatusAnnouncement(announcement))
|
||||
}
|
||||
return result
|
||||
}
|
||||
return fallback
|
||||
}
|
||||
|
||||
func buildStatusAnnouncement(announcement model.Announcement) map[string]interface{} {
|
||||
publishDate := announcement.CreatedAt
|
||||
if announcement.StartsAt != nil {
|
||||
publishDate = *announcement.StartsAt
|
||||
}
|
||||
if publishDate.IsZero() {
|
||||
publishDate = time.Now()
|
||||
}
|
||||
|
||||
return map[string]interface{}{
|
||||
"id": announcement.Id,
|
||||
"title": announcement.Title,
|
||||
"content": announcement.Content,
|
||||
"content_html": announcement.ContentHtml,
|
||||
"type": "default",
|
||||
"extra": announcement.Title,
|
||||
"publishDate": publishDate.Format(time.RFC3339),
|
||||
"status": announcement.Status,
|
||||
"notify_mode": announcement.NotifyMode,
|
||||
"json_rules": announcement.JsonRules,
|
||||
"starts_at": announcement.StartsAt,
|
||||
"ends_at": announcement.EndsAt,
|
||||
"created_at": announcement.CreatedAt,
|
||||
"updated_at": announcement.UpdatedAt,
|
||||
}
|
||||
}
|
||||
|
||||
func GetNotice(c *gin.Context) {
|
||||
common.OptionMapRWMutex.RLock()
|
||||
defer common.OptionMapRWMutex.RUnlock()
|
||||
|
||||
@ -22,6 +22,8 @@ func SetApiRouter(router *gin.Engine) {
|
||||
apiRouter.POST("/setup", controller.PostSetup)
|
||||
apiRouter.GET("/status", controller.GetStatus)
|
||||
apiRouter.GET("/uptime/status", controller.GetUptimeKumaStatus)
|
||||
apiRouter.GET("/announcements", middleware.UserAuth(), controller.GetUserAnnouncements)
|
||||
apiRouter.POST("/announcements/:id/read", middleware.UserAuth(), controller.MarkAnnouncementRead)
|
||||
apiRouter.GET("/models", middleware.UserAuth(), controller.DashboardListModels)
|
||||
apiRouter.GET("/status/test", middleware.AdminAuth(), controller.TestStatus)
|
||||
apiRouter.GET("/notice", controller.GetNotice)
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user