feat: host the BM chat context
When golem15.chat is active, Boot registers the context alias bm (id 1) on its registry. Membership is the user group jz.bm.chat.group (default studenci); CanOverseeChats is always false, so nobody reads other people's DMs or edits their messages; the broadcast channel is chat:context:1, never empty. GET /_bm/api/v1/chat answers the caller's membership behind jwt.auth and the per-user bucket jz.bm.chat (240 a minute). The chat config block (group, moderator_group, max_upload_bytes, max_embeds) is added. jz.bm still Requires only golem15.user; all chat wiring is gated by HasPlugin.
This commit is contained in:
91
classes/chat_context.go
Normal file
91
classes/chat_context.go
Normal file
@@ -0,0 +1,91 @@
|
||||
package classes
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
chatcontracts "git.golem15.com/golem15/sm-chat-plugin/contracts"
|
||||
userclasses "git.golem15.com/golem15/sm-user-plugin/classes"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
var _ chatcontracts.Context = (*ChatContext)(nil)
|
||||
|
||||
// The BM chat context: one context of type bm with id 1, whose members are
|
||||
// the members of one user group.
|
||||
const (
|
||||
// ChatContextType is the context alias (context_type) jz.bm registers on
|
||||
// golem15.chat.
|
||||
ChatContextType = "bm"
|
||||
// ChatContextID is the id of the one BM chat context.
|
||||
ChatContextID uint = 1
|
||||
// ChatGeneralName is the name of the group conversation and its channel.
|
||||
ChatGeneralName = "ogólny"
|
||||
// ChatGeneralSlug is the slug of the adopted channel.
|
||||
ChatGeneralSlug = "ogolny"
|
||||
// ChatBroadcastChannel is the host channel golem15.chat publishes created
|
||||
// events to. It must never be empty: an empty host channel silences the
|
||||
// conversation channels too. No client can subscribe to it, because the
|
||||
// chat authorizer only admits chat:conversation:{id}.
|
||||
ChatBroadcastChannel = "chat:context:1"
|
||||
)
|
||||
|
||||
// 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.
|
||||
type ChatContext struct {
|
||||
db *gorm.DB
|
||||
group string
|
||||
moderatorGroup string
|
||||
}
|
||||
|
||||
// NewChatContext returns the BM chat context over db for the chat group
|
||||
// code group and the moderator group code moderatorGroup.
|
||||
func NewChatContext(db *gorm.DB, group, moderatorGroup string) *ChatContext {
|
||||
return &ChatContext{db: db, group: group, moderatorGroup: moderatorGroup}
|
||||
}
|
||||
|
||||
func (c *ChatContext) ID() uint { return ChatContextID }
|
||||
func (c *ChatContext) Type() string { return ChatContextType }
|
||||
func (c *ChatContext) ChatEnabled() bool { return true }
|
||||
func (c *ChatContext) GroupName() string { return ChatGeneralName }
|
||||
func (c *ChatContext) BroadcastChannel() string { return ChatBroadcastChannel }
|
||||
|
||||
// CanActFor is the default: a user acts only for themself.
|
||||
func (c *ChatContext) CanActFor(ctx context.Context, actorID, memberID uint) bool {
|
||||
return chatcontracts.DefaultCanActFor(ctx, actorID, memberID)
|
||||
}
|
||||
|
||||
// CanOverseeChats is always false. golem15.chat grants an overseer read
|
||||
// access to every conversation and edits of other people's messages, which
|
||||
// no BM user gets; moderators delete through a narrower hook.
|
||||
func (c *ChatContext) CanOverseeChats(context.Context, uint) (bool, error) {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
// 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 == "" {
|
||||
return false, nil
|
||||
}
|
||||
return userclasses.HasGroupCode(ctx, c.db, userID, c.group)
|
||||
}
|
||||
|
||||
// Members lists the ids of the chat group's users that are not deleted.
|
||||
func (c *ChatContext) Members(ctx context.Context) ([]uint, error) {
|
||||
ids := []uint{}
|
||||
if c.group == "" {
|
||||
return ids, nil
|
||||
}
|
||||
err := c.db.WithContext(ctx).
|
||||
Table("users_groups").
|
||||
Joins("JOIN user_groups ON user_groups.id = users_groups.user_group_id").
|
||||
Joins("JOIN users ON users.id = users_groups.user_id").
|
||||
Where("user_groups.code = ? AND users.deleted_at IS NULL", c.group).
|
||||
Distinct().
|
||||
Order("users_groups.user_id").
|
||||
Pluck("users_groups.user_id", &ids).Error
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return ids, nil
|
||||
}
|
||||
Reference in New Issue
Block a user