feat(gateway): invite and hard-delete REST endpoints + repo methods
Wire the bus's new account surface into the admin gateway:
- POST /api/invites, GET /api/invites: mint and list single-use registration
invites (CreateInvite/ListInvites on the Repo). The gateway pre-builds the
shareable join link (JoinURL) from a configurable end-user client base URL so
the SPA does not need to know where the client lives.
- DELETE /api/users/{pub}: hard-delete (purge) a user, distinct from the existing
revoke.
- Both backends covered: signed control-plane (cluster default) via the unibus
client's CreateInvite/ListInvites/DeleteUser, and the direct membership store
(single-node --db fallback). For the direct store, ListInvites filters to
pending (the control plane already does so server-side).
- New --join-base-url flag / UNIBUS_JOIN_BASE_URL env feeds the join link base
URL (the END-USER client, NOT the panel's own URL); surfaced on /api/me.
- Mock repo gains the same methods for UI iteration.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -2,19 +2,26 @@ package admin
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/rand"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
// mockJoinBaseURL is the sample client base URL the mock uses to build join links.
|
||||
const mockJoinBaseURL = "https://chat.unibus.example"
|
||||
|
||||
// mockRepo serves sample data so the SPA can be iterated and demoed without a
|
||||
// live bus. It is selected with --mock. All mutations are kept in memory so the
|
||||
// UI feels real during a session (create a room, see it appear) without touching
|
||||
// any control plane.
|
||||
type mockRepo struct {
|
||||
mu sync.Mutex
|
||||
rooms []RoomView
|
||||
users []UserView
|
||||
mem map[string][]MemberView
|
||||
mu sync.Mutex
|
||||
rooms []RoomView
|
||||
users []UserView
|
||||
mem map[string][]MemberView
|
||||
invites []InviteView
|
||||
}
|
||||
|
||||
// NewMockRepo returns a Repo backed by in-memory sample data (--mock).
|
||||
@@ -50,6 +57,7 @@ func (m *mockRepo) Me(context.Context) MeInfo {
|
||||
SignPub: "48bc0dc829571a1332b4b2deb0bd78326a06bcf149e7d560728d8dc0b98173fa",
|
||||
UsersBackend: "sqlite",
|
||||
Mock: true,
|
||||
JoinBaseURL: mockJoinBaseURL,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -155,3 +163,58 @@ func (m *mockRepo) RevokeUser(_ context.Context, signPub string) error {
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *mockRepo) DeleteUser(_ context.Context, signPub string) error {
|
||||
m.mu.Lock()
|
||||
defer m.mu.Unlock()
|
||||
kept := m.users[:0]
|
||||
for _, u := range m.users {
|
||||
if u.SignPub != signPub {
|
||||
kept = append(kept, u)
|
||||
}
|
||||
}
|
||||
m.users = kept
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *mockRepo) CreateInvite(_ context.Context, req CreateInviteReq) (InviteView, error) {
|
||||
m.mu.Lock()
|
||||
defer m.mu.Unlock()
|
||||
role := req.Role
|
||||
if role == "" {
|
||||
role = "member"
|
||||
}
|
||||
tokRaw := make([]byte, 32)
|
||||
if _, err := rand.Read(tokRaw); err != nil {
|
||||
return InviteView{}, err
|
||||
}
|
||||
token := hex.EncodeToString(tokRaw)
|
||||
ttl := time.Duration(req.TTLSecs) * time.Second
|
||||
if req.TTLSecs <= 0 {
|
||||
ttl = 7 * 24 * time.Hour
|
||||
}
|
||||
now := time.Now().UTC()
|
||||
inv := InviteView{
|
||||
Token: token,
|
||||
Handle: req.Handle,
|
||||
Role: role,
|
||||
ExpiresAt: now.Add(ttl).Format(time.RFC3339Nano),
|
||||
Used: false,
|
||||
CreatedAt: now.Format(time.RFC3339Nano),
|
||||
JoinURL: mockJoinBaseURL + "/join?token=" + token,
|
||||
}
|
||||
m.invites = append(m.invites, inv)
|
||||
return inv, nil
|
||||
}
|
||||
|
||||
func (m *mockRepo) ListInvites(context.Context) ([]InviteView, error) {
|
||||
m.mu.Lock()
|
||||
defer m.mu.Unlock()
|
||||
out := make([]InviteView, 0, len(m.invites))
|
||||
for _, inv := range m.invites {
|
||||
if invitePending(inv.ExpiresAt, inv.Used) {
|
||||
out = append(out, inv)
|
||||
}
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user