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:
2026-10-26 15:40:44 +01:00
parent 67806f49a7
commit 64601f6817
9 changed files with 482 additions and 5 deletions

View File

@@ -11,6 +11,8 @@ import (
"sort"
"time"
chatclasses "git.golem15.com/golem15/sm-chat-plugin/classes"
chanclasses "git.golem15.com/golem15/sm-chatchannels-plugin/classes"
"git.golem15.com/golem15/summercms/modules/bouncer"
"git.golem15.com/golem15/summercms/modules/phrasebook"
"git.golem15.com/golem15/summercms/modules/wire"
@@ -41,6 +43,25 @@ type Handlers struct {
// (jz.bm.public_url), used for the calendar feed URL and the links in
// it. Empty means the scheme and Host of the request.
PublicURL string
// ChatService returns golem15.chat's service, or nil while none is published
// (golem15.chat inactive, or no database yet).
ChatService func() *chatclasses.Service
// Channels returns golem15.chatchannels' channel service, or nil while
// none is published.
Channels func() *chanclasses.ChannelService
// ChatGroup is the code of the user group whose members are in the chat
// (jz.bm.chat.group). Empty means nobody is.
ChatGroup string
// ChatModeratorGroup is the code of the chat's moderator group
// (jz.bm.chat.moderator_group).
ChatModeratorGroup string
// ChatMaxUploadBytes is the total attachment size of one chat message.
ChatMaxUploadBytes int
// ChatMaxEmbeds is the number of entity embeds one chat message may carry.
ChatMaxEmbeds int
// ChatBots is whether golem15.chatbots is active, so its is_bot flags
// can be read.
ChatBots bool
}
func (h *Handlers) now() time.Time {

36
controllers/api/chat.go Normal file
View File

@@ -0,0 +1,36 @@
package api
import (
"net/http"
userclasses "git.golem15.com/golem15/sm-user-plugin/classes"
)
// chatBootstrapJSON is the answer of /chat. A user outside the chat group
// gets only member: false.
type chatBootstrapJSON struct {
Member bool `json:"member"`
}
// Chat answers GET /chat, the chat bootstrap: whether the caller is in the
// chat group.
func (h *Handlers) Chat(w http.ResponseWriter, r *http.Request) {
userID, ok := h.caller(w, r)
if !ok {
return
}
db, ok := h.db(w, r)
if !ok {
return
}
member := false
if h.ChatGroup != "" {
var err error
member, err = userclasses.HasGroupCode(r.Context(), db, userID, h.ChatGroup)
if err != nil {
writeOpaque500(w)
return
}
}
writeData(w, chatBootstrapJSON{Member: member})
}