feat: delete-only chat moderation and embed search and resolve

- ChatContext implements golem15.chat's MessageModerator: moderator
  group members who are chat members delete in group conversations
  only, never in DMs
- GET /chat/embeds?type=deadline|class|reading&q= (members only,
  at most 20 rows, escaped ILIKE, q at most 100 characters)
- POST /chat/embeds/resolve {refs} (members only, at most 50 distinct
  refs, 8 KiB body): current data or null per ref
This commit is contained in:
2026-10-26 16:07:26 +01:00
parent ac4c2e6f73
commit 2681d304e2
3 changed files with 315 additions and 3 deletions

View File

@@ -10,8 +10,9 @@ import (
)
var (
_ chatcontracts.Context = (*ChatContext)(nil)
_ chancontracts.AccessResolver = (*ChatAccess)(nil)
_ chatcontracts.Context = (*ChatContext)(nil)
_ chatcontracts.MessageModerator = (*ChatContext)(nil)
_ chancontracts.AccessResolver = (*ChatAccess)(nil)
)
// The BM chat context: one context of type bm with id 1, whose members are
@@ -75,7 +76,8 @@ func ChatMemberIDs(ctx context.Context, db *gorm.DB, group string) ([]uint, erro
// ChatContext is the golem15.chat context of the BM chat. Membership is the
// user group named by group; nobody oversees other people's chats, so a
// moderator reads no DM and edits no foreign message.
// moderator reads no DM and edits no foreign message. Moderators delete
// through CanModerateMessage, in group conversations only.
type ChatContext struct {
db *gorm.DB
group string
@@ -113,6 +115,25 @@ func (c *ChatContext) CanOverseeChats(context.Context, uint) (bool, error) {
return false, nil
}
// CanModerateMessage is golem15.chat's delete-only moderation hook (D-10):
// a member of the moderator group who is also a chat-group member may
// delete other people's messages in group conversations (# ogólny). In a
// DM the answer is always false, so moderators never act on private
// conversations (D-24). It grants no viewing or editing.
func (c *ChatContext) CanModerateMessage(ctx context.Context, userID uint, conversationType string) (bool, error) {
if conversationType != "group" || userID == 0 || c.moderatorGroup == "" {
return false, nil
}
member, err := c.HasMember(ctx, userID)
if err != nil || !member {
return false, err
}
if c.isMember != nil {
return c.isMember(ctx, c.db, userID, c.moderatorGroup)
}
return IsChatModerator(ctx, c.db, userID, c.moderatorGroup)
}
// HasMember reports whether userID is in the chat group.
func (c *ChatContext) HasMember(ctx context.Context, userID uint) (bool, error) {
if userID == 0 || c.group == "" {