fix: rewrite backend hooks, fix invitation flow, align frontend API, fix component naming
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
+128
-128
@@ -8,13 +8,139 @@ import (
|
||||
"github.com/pocketbase/pocketbase/core"
|
||||
)
|
||||
|
||||
// isGroupMember checks if a user is a member of a group
|
||||
func main() {
|
||||
app := pocketbase.New()
|
||||
|
||||
// ── Groups ──
|
||||
|
||||
app.OnRecordBeforeCreateRequest("groups").Add(func(e *core.RecordCreateEvent) error {
|
||||
authRecord, _ := e.HttpContext.AuthRecord()
|
||||
if authRecord == nil {
|
||||
return apis.NewForbiddenError("需要登录", nil)
|
||||
}
|
||||
e.Record.Set("owner", authRecord.Id)
|
||||
e.Record.Set("members", []string{authRecord.Id})
|
||||
return nil
|
||||
})
|
||||
|
||||
app.OnRecordBeforeUpdateRequest("groups").Add(func(e *core.RecordUpdateEvent) error {
|
||||
authRecord, _ := e.HttpContext.AuthRecord()
|
||||
if authRecord == nil {
|
||||
return apis.NewForbiddenError("需要登录", nil)
|
||||
}
|
||||
original, err := app.Dao().FindRecordById("groups", e.Record.Id)
|
||||
if err != nil {
|
||||
return apis.NewNotFoundError("群组不存在", nil)
|
||||
}
|
||||
if original.GetString("owner") != authRecord.Id {
|
||||
return apis.NewForbiddenError("只有群组所有者可以更新群组", nil)
|
||||
}
|
||||
// 保护 owner 字段不被篡改
|
||||
e.Record.Set("owner", original.GetString("owner"))
|
||||
// 验证 members 数量
|
||||
members := e.Record.GetStringSlice("members")
|
||||
maxMembers := e.Record.GetInt("maxMembers")
|
||||
if maxMembers > 0 && len(members) > maxMembers {
|
||||
return apis.NewBadRequestError("成员数量超过上限", nil)
|
||||
}
|
||||
return nil
|
||||
})
|
||||
|
||||
app.OnRecordBeforeDeleteRequest("groups").Add(func(e *core.RecordDeleteEvent) error {
|
||||
authRecord, _ := e.HttpContext.AuthRecord()
|
||||
if authRecord == nil {
|
||||
return apis.NewForbiddenError("需要登录", nil)
|
||||
}
|
||||
isOwner, err := isGroupOwner(app, e.Record.Id, authRecord.Id)
|
||||
if err != nil || !isOwner {
|
||||
return apis.NewForbiddenError("只有群组所有者可以删除群组", nil)
|
||||
}
|
||||
return nil
|
||||
})
|
||||
|
||||
// ── Team Sessions ──
|
||||
|
||||
app.OnRecordBeforeCreateRequest("team_sessions").Add(func(e *core.RecordCreateEvent) error {
|
||||
authRecord, _ := e.HttpContext.AuthRecord()
|
||||
if authRecord == nil {
|
||||
return apis.NewForbiddenError("需要登录", nil)
|
||||
}
|
||||
groupId := e.Record.GetString("sourceGroup")
|
||||
isMember, err := isGroupMember(app, groupId, authRecord.Id)
|
||||
if err != nil || !isMember {
|
||||
return apis.NewForbiddenError("只有群组成员可以创建团队会话", nil)
|
||||
}
|
||||
return nil
|
||||
})
|
||||
|
||||
// ── Invitations ──
|
||||
|
||||
app.OnRecordBeforeCreateRequest("invitations").Add(func(e *core.RecordCreateEvent) error {
|
||||
authRecord, _ := e.HttpContext.AuthRecord()
|
||||
if authRecord == nil {
|
||||
return apis.NewForbiddenError("需要登录", nil)
|
||||
}
|
||||
// 强制设置 from 为当前用户
|
||||
e.Record.Set("from", authRecord.Id)
|
||||
e.Record.Set("status", "pending")
|
||||
return nil
|
||||
})
|
||||
|
||||
// 接受邀请:自动加入 team session + 更新用户状态
|
||||
app.OnRecordBeforeUpdateRequest("invitations").Add(func(e *core.RecordUpdateEvent) error {
|
||||
authRecord, _ := e.HttpContext.AuthRecord()
|
||||
if authRecord == nil {
|
||||
return apis.NewForbiddenError("需要登录", nil)
|
||||
}
|
||||
// 只有 recipient 可以更新邀请
|
||||
if e.Record.GetString("to") != authRecord.Id {
|
||||
return apis.NewForbiddenError("无权操作此邀请", nil)
|
||||
}
|
||||
|
||||
newStatus := e.Record.GetString("status")
|
||||
if newStatus == "accepted" {
|
||||
teamSessionId := e.Record.GetString("teamSession")
|
||||
teamSession, err := app.Dao().FindRecordById("team_sessions", teamSessionId)
|
||||
if err != nil {
|
||||
return apis.NewNotFoundError("临时小组不存在", nil)
|
||||
}
|
||||
// 将用户加入 team session members
|
||||
members := teamSession.GetStringSlice("members")
|
||||
alreadyIn := false
|
||||
for _, m := range members {
|
||||
if m == authRecord.Id {
|
||||
alreadyIn = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !alreadyIn {
|
||||
members = append(members, authRecord.Id)
|
||||
teamSession.Set("members", members)
|
||||
if err := app.Dao().SaveRecord(teamSession); err != nil {
|
||||
return apis.NewBadRequestError("加入临时小组失败", nil)
|
||||
}
|
||||
}
|
||||
|
||||
// 更新用户状态为 in_team
|
||||
user, err := app.Dao().FindRecordById("users", authRecord.Id)
|
||||
if err == nil {
|
||||
user.Set("status", "in_team")
|
||||
app.Dao().SaveRecord(user)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
})
|
||||
|
||||
if err := app.Start(); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
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 {
|
||||
@@ -24,7 +150,6 @@ func isGroupMember(app *pocketbase.PocketBase, groupId string, userId string) (b
|
||||
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 {
|
||||
@@ -32,128 +157,3 @@ func isGroupOwner(app *pocketbase.PocketBase, groupId string, userId string) (bo
|
||||
}
|
||||
return group.GetString("owner") == userId, nil
|
||||
}
|
||||
|
||||
func main() {
|
||||
app := pocketbase.New()
|
||||
|
||||
// Groups API Rules
|
||||
app.OnRecordBeforeCreateRequest("groups").Add(func(e *core.RecordCreateEvent) error {
|
||||
authRecord, _ := e.HttpContext.AuthRecord()
|
||||
if authRecord == nil {
|
||||
return apis.NewForbiddenError("需要登录", nil)
|
||||
}
|
||||
|
||||
// Set the owner to the current user
|
||||
e.Record.Set("owner", authRecord.Id)
|
||||
// Initialize members array with the owner
|
||||
e.Record.Set("members", []string{authRecord.Id})
|
||||
|
||||
return nil
|
||||
})
|
||||
|
||||
app.OnRecordBeforeUpdateRequest("groups").Add(func(e *core.RecordUpdateEvent) error {
|
||||
authRecord, _ := e.HttpContext.AuthRecord()
|
||||
if authRecord == nil {
|
||||
return apis.NewForbiddenError("需要登录", nil)
|
||||
}
|
||||
|
||||
// Only owner can update the group
|
||||
isOwner, err := isGroupOwner(app, e.Record.Id, authRecord.Id)
|
||||
if err != nil || !isOwner {
|
||||
return apis.NewForbiddenError("只有群组所有者可以更新群组", nil)
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
|
||||
app.OnRecordBeforeDeleteRequest("groups").Add(func(e *core.RecordDeleteEvent) error {
|
||||
authRecord, _ := e.HttpContext.AuthRecord()
|
||||
if authRecord == nil {
|
||||
return apis.NewForbiddenError("需要登录", nil)
|
||||
}
|
||||
|
||||
// Only owner can delete the group
|
||||
isOwner, err := isGroupOwner(app, e.Record.Id, authRecord.Id)
|
||||
if err != nil || !isOwner {
|
||||
return apis.NewForbiddenError("只有群组所有者可以删除群组", nil)
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
|
||||
// Team Sessions API Rules
|
||||
app.OnRecordBeforeCreateRequest("team_sessions").Add(func(e *core.RecordCreateEvent) error {
|
||||
authRecord, _ := e.HttpContext.AuthRecord()
|
||||
if authRecord == nil {
|
||||
return apis.NewForbiddenError("需要登录", nil)
|
||||
}
|
||||
|
||||
groupId := e.Record.GetString("group")
|
||||
|
||||
// Check if user is a member of the group
|
||||
isMember, err := isGroupMember(app, groupId, authRecord.Id)
|
||||
if err != nil || !isMember {
|
||||
return apis.NewForbiddenError("只有群组成员可以创建团队会话", nil)
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
|
||||
// Invitations API Rules
|
||||
app.OnRecordBeforeCreateRequest("invitations").Add(func(e *core.RecordCreateEvent) error {
|
||||
authRecord, _ := e.HttpContext.AuthRecord()
|
||||
if authRecord == nil {
|
||||
return apis.NewForbiddenError("需要登录", nil)
|
||||
}
|
||||
|
||||
groupId := e.Record.GetString("group")
|
||||
|
||||
// Only group owner can create invitations
|
||||
isOwner, err := isGroupOwner(app, groupId, authRecord.Id)
|
||||
if err != nil || !isOwner {
|
||||
return apis.NewForbiddenError("只有群组所有者可以创建邀请", nil)
|
||||
}
|
||||
|
||||
// Set status to pending
|
||||
e.Record.Set("status", "pending")
|
||||
|
||||
return nil
|
||||
})
|
||||
|
||||
app.OnRecordAfterCreateRequest("invitations").Add(func(e *core.RecordCreateEvent) error {
|
||||
// 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.Subscriptions().Broadcast("invitations", message); err != nil {
|
||||
log.Printf("Error broadcasting invitation: %v", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
|
||||
// Real-time subscription rules
|
||||
app.OnRecordAfterAuthWithTokenRequest().Add(func(e *core.RecordAuthEvent) error {
|
||||
// Subscribe to invitations channel and user's groups channel
|
||||
app.Subscriptions().Subscribe(e.HttpContext.Response(), []string{
|
||||
"invitations",
|
||||
"groups:" + e.Record.Id,
|
||||
"team_sessions",
|
||||
})
|
||||
|
||||
return nil
|
||||
})
|
||||
|
||||
if err := app.Start(); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user