feat: 书库导入导出/图片变体/书籍搜索/小组件运行时等
新增: - 书库导入导出(library_import/library_export)及测试 - 图片变体生成(image_variants)与响应式图片(responsiveImage) - 书籍搜索(bookSearch)+ BookSearch/LibrarySearchGrid 组件 - 小组件运行时(widgetRuntime)与静态检查(widgetLint) - 上传缓存中间件(upload_cache)与字体 CSS 提取脚本 其它: - 后端 handlers/services 全量调整 - 前端页面、组件、库函数与配置更新
This commit is contained in:
@@ -75,8 +75,8 @@ type RoomView struct {
|
||||
Joined bool `json:"joined"`
|
||||
MyRole string `json:"my_role"`
|
||||
UnreadCount int64 `json:"unread_count"`
|
||||
Pinned bool `json:"pinned"` // 对当前用户是否置顶(含大厅强制)
|
||||
PinForced bool `json:"pin_forced"` // 大厅强制置顶,不可取消
|
||||
Pinned bool `json:"pinned"` // 对当前用户是否置顶(含大厅强制)
|
||||
PinForced bool `json:"pin_forced"` // 大厅强制置顶,不可取消
|
||||
Peer *model.User `json:"peer,omitempty"` // 私聊对方(仅 direct)
|
||||
}
|
||||
|
||||
@@ -114,6 +114,15 @@ func (s *ChatService) membership(tx *gorm.DB, roomID, userID uint) (*model.ChatR
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// 过期禁言自动解除:惰性清理,不依赖定时任务
|
||||
if m.Muted && m.MutedUntil != nil && m.MutedUntil.Before(time.Now()) {
|
||||
m.Muted = false
|
||||
m.MutedUntil = nil
|
||||
_ = tx.Model(&model.ChatRoomMember{}).
|
||||
Where("room_id = ? AND user_id = ?", roomID, userID).
|
||||
Select("muted", "muted_until").
|
||||
Updates(map[string]interface{}{"muted": false, "muted_until": nil}).Error
|
||||
}
|
||||
return &m, nil
|
||||
}
|
||||
|
||||
@@ -758,44 +767,85 @@ func (s *ChatService) Kick(operatorID, roomID, targetID uint, oversee bool) erro
|
||||
return s.removeMember(roomID, targetID)
|
||||
}
|
||||
|
||||
// SetMemberMute 禁言/解禁:群主、群管、全站监管;不可禁言群主或站点站长账号
|
||||
func (s *ChatService) SetMemberMute(operatorID, roomID, targetID uint, muted, oversee bool) error {
|
||||
// SendSystemMessage 向房间落库一条系统提示消息(sender_id=0,is_system=true),
|
||||
// 并推进房间 last_message_id 与 updated_at。失败返回 nil + error。
|
||||
func (s *ChatService) SendSystemMessage(roomID uint, content string) (*model.ChatMessage, error) {
|
||||
msg := &model.ChatMessage{
|
||||
RoomID: roomID,
|
||||
SenderID: 0,
|
||||
Content: content,
|
||||
IsSystem: true,
|
||||
CreatedAt: time.Now(),
|
||||
}
|
||||
err := s.db.Transaction(func(tx *gorm.DB) error {
|
||||
if err := tx.Create(msg).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
return tx.Model(&model.ChatRoom{}).Where("id = ?", roomID).
|
||||
Updates(map[string]interface{}{
|
||||
"last_message_id": msg.ID,
|
||||
"updated_at": time.Now(),
|
||||
}).Error
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return msg, nil
|
||||
}
|
||||
|
||||
// SetMemberMute 禁言/解禁:群主、群管、全站监管;不可禁言群主或站点站长账号。
|
||||
// duration 为禁言时长;0 表示永久禁言;muted=false 时忽略 duration 并清空 muted_until。
|
||||
// 返回目标用户昵称(供广播系统提示使用)。
|
||||
func (s *ChatService) SetMemberMute(operatorID, roomID, targetID uint, muted bool, duration time.Duration, oversee bool) (string, error) {
|
||||
room, err := s.getRoom(roomID)
|
||||
if err != nil {
|
||||
return err
|
||||
return "", err
|
||||
}
|
||||
if room.RoomType == model.ChatRoomTypeDirect {
|
||||
return ErrChatOwnerOnly
|
||||
return "", ErrChatOwnerOnly
|
||||
}
|
||||
target, err := s.membership(s.db, roomID, targetID)
|
||||
if err != nil {
|
||||
return err
|
||||
return "", err
|
||||
}
|
||||
if target.Role == model.ChatRoleOwner {
|
||||
return ErrChatCannotMuteOwner
|
||||
return "", ErrChatCannotMuteOwner
|
||||
}
|
||||
var targetUser model.User
|
||||
if err := s.db.Select("role").First(&targetUser, targetID).Error; err != nil {
|
||||
return ErrChatUserGone
|
||||
if err := s.db.Select("role", "nickname", "username").First(&targetUser, targetID).Error; err != nil {
|
||||
return "", ErrChatUserGone
|
||||
}
|
||||
if targetUser.Role == model.RoleOwner {
|
||||
return ErrChatCannotMuteOwner
|
||||
return "", ErrChatCannotMuteOwner
|
||||
}
|
||||
if !oversee {
|
||||
op, err := s.membership(s.db, roomID, operatorID)
|
||||
if err != nil {
|
||||
return err
|
||||
return "", err
|
||||
}
|
||||
if op.Role != model.ChatRoleOwner && op.Role != model.ChatRoleAdmin {
|
||||
return ErrChatMuteDenied
|
||||
return "", ErrChatMuteDenied
|
||||
}
|
||||
if target.Role == model.ChatRoleAdmin && op.Role != model.ChatRoleOwner {
|
||||
return ErrChatMuteDenied
|
||||
return "", ErrChatMuteDenied
|
||||
}
|
||||
}
|
||||
return s.db.Model(&model.ChatRoomMember{}).
|
||||
var mutedUntil *time.Time
|
||||
if muted && duration > 0 {
|
||||
t := time.Now().Add(duration)
|
||||
mutedUntil = &t
|
||||
}
|
||||
if err := s.db.Model(&model.ChatRoomMember{}).
|
||||
Where("room_id = ? AND user_id = ?", roomID, targetID).
|
||||
Select("muted").Update("muted", muted).Error
|
||||
Select("muted", "muted_until").
|
||||
Updates(map[string]interface{}{"muted": muted, "muted_until": mutedUntil}).Error; err != nil {
|
||||
return "", err
|
||||
}
|
||||
targetName := targetUser.Nickname
|
||||
if targetName == "" {
|
||||
targetName = targetUser.Username
|
||||
}
|
||||
return targetName, nil
|
||||
}
|
||||
|
||||
// SetMemberRole 仅站点站长可任命/撤销群管理员(admin <-> member);不可改群主
|
||||
|
||||
Reference in New Issue
Block a user