Files
ttstd-infinite-canvas/backend/internal/repository/announcement.go

186 lines
6.4 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package repository
import (
"encoding/json"
"strings"
"time"
"infinite-canvas/backend/internal/model"
"gorm.io/gorm"
"gorm.io/gorm/clause"
)
func (r *Repository) AdminAnnouncements(keyword string, status model.AnnouncementStatus, limit int, offset int) ([]model.Announcement, int64, error) {
var announcements []model.Announcement
var total int64
query := r.db.Model(&model.Announcement{})
if value := strings.TrimSpace(keyword); value != "" {
pattern := "%" + strings.ToLower(value) + "%"
query = query.Where("lower(title) LIKE ? OR lower(content) LIKE ?", pattern, pattern)
}
if status == model.AnnouncementStatusActive || status == model.AnnouncementStatusClosed {
query = query.Where("status = ?", status)
}
if err := query.Count(&total).Error; err != nil {
return nil, 0, err
}
if err := query.Order("pinned desc, published_at desc").Limit(limit).Offset(offset).Find(&announcements).Error; err != nil {
return nil, 0, err
}
return announcements, total, nil
}
func (r *Repository) AnnouncementFeed(userID string) ([]model.Announcement, int64, error) {
var announcements []model.Announcement
var unreadCount int64
err := r.db.Transaction(func(tx *gorm.DB) error {
if err := tx.Where("status = ?", model.AnnouncementStatusActive).Order("pinned desc, published_at desc").Find(&announcements).Error; err != nil {
return err
}
return tx.Model(&model.Announcement{}).
Joins("LEFT JOIN user_announcement_reads ON user_announcement_reads.announcement_id = announcements.id AND user_announcement_reads.user_id = ?", userID).
Where("announcements.status = ? AND user_announcement_reads.id IS NULL", model.AnnouncementStatusActive).
Count(&unreadCount).Error
})
return announcements, unreadCount, err
}
func (r *Repository) Announcement(id string) (*model.Announcement, error) {
var announcement model.Announcement
if err := r.db.First(&announcement, "id = ?", id).Error; err != nil {
return nil, err
}
return &announcement, nil
}
func (r *Repository) CloseAnnouncement(id string, closedAt time.Time) (bool, error) {
result := r.db.Model(&model.Announcement{}).
Where("id = ? AND status = ?", id, model.AnnouncementStatusActive).
Updates(map[string]any{"status": model.AnnouncementStatusClosed, "closed_at": closedAt, "updated_at": closedAt})
return result.RowsAffected == 1, result.Error
}
func (r *Repository) MarkAnnouncementsRead(userID string, announcementIDs []string, readAt time.Time) error {
if len(announcementIDs) == 0 {
return nil
}
var activeIDs []string
if err := r.db.Model(&model.Announcement{}).Where("id IN ? AND status = ?", announcementIDs, model.AnnouncementStatusActive).Pluck("id", &activeIDs).Error; err != nil {
return err
}
if len(activeIDs) == 0 {
return nil
}
reads := make([]model.UserAnnouncementRead, 0, len(activeIDs))
for _, id := range activeIDs {
reads = append(reads, model.UserAnnouncementRead{ID: newRepositoryID(), UserID: userID, AnnouncementID: id, ReadAt: readAt})
}
return r.db.Clauses(clause.OnConflict{Columns: []clause.Column{{Name: "user_id"}, {Name: "announcement_id"}}, DoNothing: true}).Create(&reads).Error
}
func (r *Repository) ActiveBannerAnnouncements(now time.Time) ([]model.BannerAnnouncement, error) {
var banners []model.BannerAnnouncement
err := r.db.Model(&model.BannerAnnouncement{}).
Where("status = ?", "active").
Where("starts_at IS NULL OR starts_at <= ?", now).
Where("ends_at IS NULL OR ends_at >= ?", now).
Order("created_at desc").
Find(&banners).Error
decodeBannerTitleRuns(banners)
return banners, err
}
func (r *Repository) AdminBannerAnnouncements(keyword string, status string, limit int, offset int) ([]model.BannerAnnouncement, int64, error) {
var banners []model.BannerAnnouncement
var total int64
query := r.db.Model(&model.BannerAnnouncement{})
if k := strings.TrimSpace(keyword); k != "" {
query = query.Where("title LIKE ?", "%"+k+"%")
}
if status == "active" || status == "disabled" {
query = query.Where("status = ?", status)
}
if err := query.Count(&total).Error; err != nil {
return nil, 0, err
}
if err := query.Order("created_at desc").Limit(limit).Offset(offset).Find(&banners).Error; err != nil {
return nil, 0, err
}
decodeBannerTitleRuns(banners)
return banners, total, nil
}
func (r *Repository) BannerAnnouncement(id string) (*model.BannerAnnouncement, error) {
var banner model.BannerAnnouncement
if err := r.db.First(&banner, "id = ?", id).Error; err != nil {
return nil, err
}
banner.TitleRuns = bannerTitleRunsFromJSON(banner.TitleRunsJSON)
return &banner, nil
}
// encodeBannerTitleRuns 把样式分段写入 title_runs 文本列;无分段时写空串,与「纯文本标题」区分。
func encodeBannerTitleRuns(banner *model.BannerAnnouncement) error {
if len(banner.TitleRuns) == 0 {
banner.TitleRunsJSON = ""
return nil
}
encoded, err := json.Marshal(banner.TitleRuns)
if err != nil {
return err
}
banner.TitleRunsJSON = string(encoded)
return nil
}
// decodeBannerTitleRuns 回填列表项的样式分段。
func decodeBannerTitleRuns(banners []model.BannerAnnouncement) {
for index := range banners {
banners[index].TitleRuns = bannerTitleRunsFromJSON(banners[index].TitleRunsJSON)
}
}
// bannerTitleRunsFromJSON 解析 title_runs 列文本;空值或内容损坏时返回 nil,降级为纯文本标题,不阻断通知条展示。
func bannerTitleRunsFromJSON(raw string) []model.BannerTitleRun {
raw = strings.TrimSpace(raw)
if raw == "" {
return nil
}
var runs []model.BannerTitleRun
if err := json.Unmarshal([]byte(raw), &runs); err != nil {
return nil
}
return runs
}
func (r *Repository) CreateBannerAnnouncement(banner *model.BannerAnnouncement) error {
if err := encodeBannerTitleRuns(banner); err != nil {
return err
}
return r.db.Create(banner).Error
}
func (r *Repository) UpdateBannerAnnouncement(banner *model.BannerAnnouncement) error {
if err := encodeBannerTitleRuns(banner); err != nil {
return err
}
updates := map[string]any{
"title": banner.Title,
"title_runs": banner.TitleRunsJSON,
"notice_type": banner.NoticeType,
"link": banner.Link,
"status": banner.Status,
"updated_at": banner.UpdatedAt,
"starts_at": banner.StartsAt,
"ends_at": banner.EndsAt,
}
return r.db.Model(&model.BannerAnnouncement{}).
Where("id = ?", banner.ID).
Updates(updates).Error
}
func (r *Repository) DeleteBannerAnnouncement(id string) error {
return r.db.Where("id = ?", id).Delete(&model.BannerAnnouncement{}).Error
}