feat: add API rules and hooks

Add PocketBase hooks for group management and real-time notifications:
- Groups collection with owner/members permissions
- Team Sessions with group member verification
- Invitations with real-time notifications
- Helper functions for group ownership and membership checks

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
congsh
2026-04-17 14:16:56 +08:00
parent b79046ec63
commit 8d4b9a167c
2 changed files with 149 additions and 0 deletions
+5
View File
@@ -0,0 +1,5 @@
module gamegroup-hooks
go 1.21
require github.com/pocketbase/pocketbase v0.22.4
+144
View File
@@ -0,0 +1,144 @@
package main
import (
"log"
"strings"
"github.com/pocketbase/pocketbase"
"github.com/pocketbase/pocketbase/core"
"github.com/pocketbase/pocketbase/models"
"github.com/pocketbase/pocketbase/tools/types"
)
// isGroupMember checks if a user is a member of a group
func isGroupMember(app *pocketbase.PocketBase, groupId string, userId string) (bool, error) {
group, err := app.Dao().FindRecordById("groups", groupId)
if err != nil {
return false, err
}
members := group.GetStringSlice("members")
for _, member := range members {
if member == userId {
return true, nil
}
}
return false, nil
}
// isGroupOwner checks if a user is the owner of a group
func isGroupOwner(app *pocketbase.PocketBase, groupId string, userId string) (bool, error) {
group, err := app.Dao().FindRecordById("groups", groupId)
if err != nil {
return false, err
}
return group.GetString("owner") == userId, nil
}
func main() {
app := pocketbase.New()
// Groups API Rules
app.OnRecordBeforeCreateRequest("groups").Add(func(e *core.RecordCreateEvent) next func() {
// Set the owner to the current user
e.Record.Set("owner", e.HttpContext.Request().Context().Value("user_id"))
// Initialize members array with the owner
e.Record.Set("members", []string{e.Record.GetString("owner")})
return next
})
app.OnRecordBeforeUpdateRequest("groups").Add(func(e *core.RecordUpdateEvent) next func() {
userId := e.HttpContext.Request().Context().Value("user_id").(string)
// Only owner can update the group
isOwner, err := isGroupOwner(app, e.Record.Id, userId)
if err != nil || !isOwner {
e.HttpContext.Response().WriteHeader(403)
return nil
}
return next
})
app.OnRecordBeforeDeleteRequest("groups").Add(func(e *core.RecordDeleteEvent) next func() {
userId := e.HttpContext.Request().Context().Value("user_id").(string)
// Only owner can delete the group
isOwner, err := isGroupOwner(app, e.Record.Id, userId)
if err != nil || !isOwner {
e.HttpContext.Response().WriteHeader(403)
return nil
}
return next
})
// Team Sessions API Rules
app.OnRecordBeforeCreateRequest("team_sessions").Add(func(e *core.RecordCreateEvent) next func() {
userId := e.HttpContext.Request().Context().Value("user_id").(string)
groupId := e.Record.GetString("group")
// Check if user is a member of the group
isMember, err := isGroupMember(app, groupId, userId)
if err != nil || !isMember {
e.HttpContext.Response().WriteHeader(403)
return nil
}
return next
})
// Invitations API Rules
app.OnRecordBeforeCreateRequest("invitations").Add(func(e *core.RecordCreateEvent) next func() {
userId := e.HttpContext.Request().Context().Value("user_id").(string)
groupId := e.Record.GetString("group")
// Only group owner can create invitations
isOwner, err := isGroupOwner(app, groupId, userId)
if err != nil || !isOwner {
e.HttpContext.Response().WriteHeader(403)
return nil
}
// Set status to pending
e.Record.Set("status", "pending")
return next
})
app.OnRecordAfterCreateRequest("invitations").Add(func(e *core.RecordCreateEvent) next func() {
// Send real-time notification to the invited user
message := map[string]interface{}{
"action": "invitation",
"data": map[string]interface{}{
"id": e.Record.Id,
"group": e.Record.GetString("group"),
"invited_by": e.Record.GetString("invited_by"),
"status": e.Record.GetString("status"),
"created": e.Record.Created.Time(),
},
}
// Broadcast to the invited user's channel
if err := app.Realtime().Broadcast("invitations", message); err != nil {
log.Printf("Error broadcasting invitation: %v", err)
}
return next
})
// Real-time subscription rules
app.OnRecordAfterAuthWithTokenRequest().Add(func(e *core.RecordAuthEvent) next func() {
// Subscribe to invitations channel
e.HttpContext.Request().Context().Set("realtime_subscriptions", []string{
"invitations",
"groups:" + e.Record.Id,
"team_sessions",
})
return next
})
// Custom API endpoint for accepting invitations
app.OnServe().Bind(&ServeEvent{
App: app,
})
}
type ServeEvent struct {
App *pocketbase.PocketBase
}