feat(api): op-login 游戏内审批先展示目标账号、邮箱与发起来源,须输入账户名确认,velocity 显示审批卡片

This commit is contained in:
Lemon-miaow committed 2026-09-25 17:02:43 +08:00
1 parent 4757353324
commit d93c1b6913
21 files changed
+1088 -176

No files matched your search

+16 -13
View File
@@ -263,6 +263,8 @@ type fakeOpLogin struct {
consumed bool
expiresAt time.Time
createdAt time.Time
clientIP string
userAgent string
}
// fakeSetupToken mirrors a setup_tokens row (spec §B setup): a one-time
@@ -1606,25 +1608,27 @@ func (f *fakeRepo) ConsumeLoginEmailOTP(_ context.Context, userID, purpose, code
// CreateOpLoginRequest records a fresh pending op.console login attempt. status is
// born 'pending'; createdAt orders the pending list (the PG ORDER BY created_at).
func (f *fakeRepo) CreateOpLoginRequest(_ context.Context, id, userID, email string, expiresAt time.Time) error {
f.opLogins[id] = &fakeOpLogin{
id: id, userID: userID, email: email, status: "pending",
expiresAt: expiresAt, createdAt: expiresAt, // createdAt proxy: constant TTL ⇒ later expiry == later creation
func (f *fakeRepo) CreateOpLoginRequest(_ context.Context, req NewOpLoginRequest) error {
f.opLogins[req.ID] = &fakeOpLogin{
id: req.ID, userID: req.UserID, email: req.Email, status: "pending",
expiresAt: req.ExpiresAt, createdAt: req.ExpiresAt, // createdAt proxy: constant TTL ⇒ later expiry == later creation
clientIP: req.ClientIP, userAgent: req.UserAgent,
}
return nil
}
// OpLoginRequestByID loads a request by handle, projecting the fake row into the
// OpLoginRequest the status/finish paths read (Status, Consumed, ExpiresAt). Status
// is the (approved_at, denied_at) projection the handler gates on.
// OpLoginRequest the handlers read, with the username joined like the PG query.
// Status is the (approved_at, denied_at) projection the handler gates on.
func (f *fakeRepo) OpLoginRequestByID(_ context.Context, id string) (*OpLoginRequest, error) {
r, ok := f.opLogins[id]
if !ok {
return nil, ErrNotFound
}
return &OpLoginRequest{
ID: r.id, UserID: r.userID, Email: r.email, ExpiresAt: r.expiresAt,
Status: r.status, Consumed: r.consumed,
ID: r.id, UserID: r.userID, Username: f.usernameFor(r.userID), Email: r.email,
ExpiresAt: r.expiresAt, CreatedAt: r.createdAt, Status: r.status, Consumed: r.consumed,
ClientIP: r.clientIP, UserAgent: r.userAgent,
}, nil
}
@@ -1644,8 +1648,7 @@ func (f *fakeRepo) ConsumeOpLoginRequest(_ context.Context, id string, now time.
// ListPendingOpLogins returns the live (pending, unconsumed, unexpired) requests
// oldest-first, mirroring the PG WHERE consumed_at IS NULL AND approved_at IS NULL
// AND expires_at > now ORDER BY created_at. Username is joined from the staff map
// (the in-game admin needs to name who is waiting), exactly as the repo.go contract
// documents — ListPendingOpLogins is the ONLY path that populates Username.
// (the in-game admin needs to name who is waiting).
func (f *fakeRepo) ListPendingOpLogins(_ context.Context, now time.Time) ([]OpLoginRequest, error) {
var out []OpLoginRequest
for _, r := range f.opLogins {
@@ -1655,6 +1658,7 @@ func (f *fakeRepo) ListPendingOpLogins(_ context.Context, now time.Time) ([]OpLo
out = append(out, OpLoginRequest{
ID: r.id, UserID: r.userID, Username: f.usernameFor(r.userID),
Email: r.email, ExpiresAt: r.expiresAt, Status: "pending", CreatedAt: r.createdAt,
ClientIP: r.clientIP, UserAgent: r.userAgent,
})
}
sort.Slice(out, func(i, j int) bool {
@@ -1690,9 +1694,8 @@ func (f *fakeRepo) ConsumeSetupToken(_ context.Context, tokenHash string, now ti
return tok.UserID, nil
}
// usernameFor joins a userID to its staff username (the ListPendingOpLogins
// projection the in-game admin needs to name who is waiting). "" when the user is
// gone — mirroring a missing JOIN row.
// usernameFor joins a userID to its staff username (the op-login projection the
// in-game admin needs to name who is waiting). "" when the user is gone.
func (f *fakeRepo) usernameFor(userID string) string {
for _, u := range f.staff {
if u.ID == userID {