feat: sync account freeze lifecycle

This commit is contained in:
A 2026-07-15 20:24:37 +08:00
parent 76bfc5100f
commit 47fcf0ea41
40 changed files with 1363 additions and 196 deletions

View file

@ -20,7 +20,7 @@ type Config struct {
}
type Service interface {
SetSendFrozen(ctx context.Context, req admin.SetSendFrozenRequest) (admin.CommandResult, error)
SetAccountFrozen(ctx context.Context, req admin.SetAccountFrozenRequest) (admin.CommandResult, error)
GrantPremium(ctx context.Context, req admin.GrantPremiumRequest) (admin.CommandResult, error)
GrantStars(ctx context.Context, req admin.GrantStarsRequest) (admin.CommandResult, error)
SetVerified(ctx context.Context, req admin.SetVerifiedRequest) (admin.CommandResult, error)
@ -76,7 +76,7 @@ func (s *Server) routes() http.Handler {
mux.HandleFunc("GET /healthz", func(w http.ResponseWriter, r *http.Request) {
writeJSON(w, http.StatusOK, map[string]string{"status": "ok"})
})
mux.HandleFunc("POST /v1/accounts/freeze-send", s.authenticated(s.handleFreezeSend))
mux.HandleFunc("POST /v1/accounts/set-frozen", s.authenticated(s.handleSetAccountFrozen))
mux.HandleFunc("POST /v1/accounts/grant-premium", s.authenticated(s.handleGrantPremium))
mux.HandleFunc("POST /v1/accounts/grant-stars", s.authenticated(s.handleGrantStars))
mux.HandleFunc("POST /v1/accounts/set-verified", s.authenticated(s.handleSetVerified))
@ -98,12 +98,12 @@ func (s *Server) authenticated(next http.HandlerFunc) http.HandlerFunc {
}
}
func (s *Server) handleFreezeSend(w http.ResponseWriter, r *http.Request) {
var req admin.SetSendFrozenRequest
func (s *Server) handleSetAccountFrozen(w http.ResponseWriter, r *http.Request) {
var req admin.SetAccountFrozenRequest
if !decodeJSON(w, r, &req) {
return
}
result, err := s.svc.SetSendFrozen(r.Context(), req)
result, err := s.svc.SetAccountFrozen(r.Context(), req)
writeCommandResult(w, result, err)
}

View file

@ -12,7 +12,7 @@ import (
func TestAdminAPIRequiresBearerToken(t *testing.T) {
srv := &Server{token: "secret", svc: fakeService{}}
req := httptest.NewRequest(http.MethodPost, "/v1/accounts/freeze-send", strings.NewReader(`{}`))
req := httptest.NewRequest(http.MethodPost, "/v1/accounts/set-frozen", strings.NewReader(`{}`))
rec := httptest.NewRecorder()
srv.routes().ServeHTTP(rec, req)
if rec.Code != http.StatusUnauthorized {
@ -20,9 +20,10 @@ func TestAdminAPIRequiresBearerToken(t *testing.T) {
}
}
func TestAdminAPIFreezeSend(t *testing.T) {
srv := &Server{token: "secret", svc: fakeService{}}
req := httptest.NewRequest(http.MethodPost, "/v1/accounts/freeze-send", strings.NewReader(`{"command_id":"c1","actor":"ops","reason":"test","dry_run":true,"user_id":1001,"frozen":true}`))
func TestAdminAPISetAccountFrozen(t *testing.T) {
svc := &captureFreezeService{}
srv := &Server{token: "secret", svc: svc}
req := httptest.NewRequest(http.MethodPost, "/v1/accounts/set-frozen", strings.NewReader(`{"command_id":"c1","actor":"ops","reason":"test","dry_run":true,"user_id":1001,"frozen":true,"freeze_until":"2030-01-02T00:00:00Z","freeze_appeal_url":"https://appeals.example.test"}`))
req.Header.Set("Authorization", "Bearer secret")
rec := httptest.NewRecorder()
srv.routes().ServeHTTP(rec, req)
@ -32,6 +33,9 @@ func TestAdminAPIFreezeSend(t *testing.T) {
if !strings.Contains(rec.Body.String(), `"command_id":"c1"`) {
t.Fatalf("body=%s", rec.Body.String())
}
if svc.req.UserID != 1001 || !svc.req.Frozen || svc.req.Until.IsZero() || svc.req.AppealURL != "https://appeals.example.test" {
t.Fatalf("decoded freeze request = %+v", svc.req)
}
}
func TestAdminAPISetVerified(t *testing.T) {
@ -78,7 +82,17 @@ func TestAdminAPISetChannelVerified(t *testing.T) {
type fakeService struct{}
func (fakeService) SetSendFrozen(_ context.Context, req admin.SetSendFrozenRequest) (admin.CommandResult, error) {
type captureFreezeService struct {
fakeService
req admin.SetAccountFrozenRequest
}
func (s *captureFreezeService) SetAccountFrozen(_ context.Context, req admin.SetAccountFrozenRequest) (admin.CommandResult, error) {
s.req = req
return admin.CommandResult{CommandID: req.CommandID, Status: "completed", DryRun: req.DryRun}, nil
}
func (fakeService) SetAccountFrozen(_ context.Context, req admin.SetAccountFrozenRequest) (admin.CommandResult, error) {
return admin.CommandResult{CommandID: req.CommandID, Status: "completed", DryRun: req.DryRun}, nil
}