Unverified Commit 56a4bbcf authored by Lemon-miaow's avatar Lemon-miaow
Browse files

refactor(api): 删掉没有调用方的 QuotaAvailable,修正 passkey last_used_at 的过时注释

parent 4839d52f
Loading
Loading
Loading
Loading
+2 −1
Changes for docs/sequence-diagrams.md: 2 added lines, 1 removed line.
Original line number Diff line number Diff line
@@ -80,7 +80,8 @@ sequenceDiagram
        API-->>Panel: 412 not_linked
    else linked
        Repo-->>API: true
        API->>Repo: QuotaAvailable(user_id)
        API->>Repo: QuotaCheck(user_id, the server's real size)
        Note over API,Repo: all four caps: servers, CPU, memory, storage
        alt quota exhausted
            Repo-->>API: false
            API-->>Panel: 403 quota_exceeded
+4 −5
Changes for internal/api/api_test.go: 4 added lines, 5 removed lines.
Original line number Diff line number Diff line
@@ -336,14 +336,13 @@ func (f *fakeRepo) ServerByName(_ context.Context, n string) (*ServerRecord, err
	return nil, ErrNotFound
}
func (f *fakeRepo) IsLinked(_ context.Context, u string) (bool, error) { return f.linked[u], nil }
func (f *fakeRepo) QuotaAvailable(_ context.Context, u string) (bool, error) { return f.quota[u], nil }

func (f *fakeRepo) QuotaCheck(_ context.Context, userID string, _ string, incoming ResourceSpec) (bool, error) {
	f.quotaChecked = append(f.quotaChecked, incoming)
	// For hermetic tests, QuotaCheck delegates to the same QuotaAvailable
	// store — tests that care about per-dimension checks should use
	// fakeQuotas with direct inspection.
	return f.QuotaAvailable(context.TODO(), userID)
	// For hermetic tests, QuotaCheck answers from the per-user quota flag —
	// tests that care about per-dimension checks should use fakeQuotas with
	// direct inspection.
	return f.quota[userID], nil
}

func (f *fakeRepo) UpdateServerResources(ctx context.Context, name string, cpu, mem, stor int) error {
+0 −26
Changes for internal/api/pgrepo.go: 0 added lines, 26 removed lines.
Original line number Diff line number Diff line
@@ -346,32 +346,6 @@ func (p *PGRepo) CompleteOwnerSetup(ctx context.Context, newUserID, code string,
	return userID, mcUUID, authSource, nil
}

// QuotaAvailable treats a missing quota row or a NULL max_servers as unlimited;
// otherwise it compares the live owned-server count against the cap (spec §9.3).
// It is the single-dimension convenience read; handlers use the four-dimension
// QuotaCheck. The former audit-#4 TOCTOU (check and claim in separate statements)
// is closed inside ClaimServer, which re-runs the four-dimension gate under a
// per-user advisory lock in the SAME transaction as the ownership write.
func (p *PGRepo) QuotaAvailable(ctx context.Context, userID string) (bool, error) {
	var maxServers sql.NullInt64
	switch err := p.db.QueryRowContext(ctx,
		`SELECT max_servers FROM quotas WHERE user_id = $1`, userID).Scan(&maxServers); {
	case errors.Is(err, sql.ErrNoRows):
		return true, nil
	case err != nil:
		return false, err
	}
	if !maxServers.Valid {
		return true, nil
	}
	var n int64
	if err := p.db.QueryRowContext(ctx,
		`SELECT count(*) FROM servers WHERE owner_id = $1 AND deleted_at IS NULL`, userID).Scan(&n); err != nil {
		return false, err
	}
	return n < maxServers.Int64, nil
}

// QuotaCheck reports whether accepting a server with resource spec `incoming`
// would push userID over any quota cap. excludeName is the server row whose own
// cached resources should be excluded ("" for a fresh claim where the row
+2 −6
Changes for internal/api/repo.go: 2 added lines, 6 removed lines.
Original line number Diff line number Diff line
@@ -166,9 +166,8 @@ type StaffUser struct {
// be public (unlike a session token), so it is safe at rest. CredentialID is the
// authenticator's globally-unique handle (base64url) and PublicKey the COSE key
// (base64); SignCount is the uint32 signature counter captured at registration.
// LastUsedAt is nil until an assertion stamps it. The passkey login door now exists
// (Public /auth/passkey/login/{begin,finish}, #72), but no path yet writes
// last_used_at, so in practice it stays nil; wiring the stamp is a follow-up there.
// LastUsedAt is nil until a passkey sign-in or step-up stamps it
// (AdvanceCredentialSignCount, with the advanced counter).
type PasskeyCredential struct {
	ID           string
	UserID       string
@@ -326,9 +325,6 @@ type Repo interface {
	// the API clock so expiry is testable. It returns the effective userID plus the
	// bound mc_uuid and authSource (for the response + audit).
	RedeemPlayerBindCode(ctx context.Context, newUserID, code string, now time.Time) (userID, mcUUID, authSource string, err error)
	// QuotaAvailable reports whether the user is under their max_servers quota
	// (spec §9.3 step ②, evaluated before provisioning).
	QuotaAvailable(ctx context.Context, userID string) (bool, error)
	// QuotaCheck reports whether claiming a server with the given resource spec
	// would push the user over any of their four quota caps: max_servers,
	// max_cpu_milli, max_memory_mb, and max_storage_gb (spec §9.3 / §22). A nil
+0 −6
Changes for internal/pgint/pgint_test.go: 0 added lines, 6 removed lines.
Original line number Diff line number Diff line
@@ -605,9 +605,6 @@ func TestSetQuotasReplacesEveryCap(t *testing.T) {

	set(api.QuotaInput{MaxServers: n(0), MaxCPUMilli: n(2000), MaxMemoryMB: n(4096), MaxStorageGB: n(20)}, "0/2000/4096/20")
	gate(false)
	if ok, err := repo.QuotaAvailable(ctx, u.ID); err != nil || ok {
		t.Fatalf("QuotaAvailable at max_servers 0 = %v, %v; want false", ok, err)
	}
	if _, err := repo.ClaimServer(ctx, name, u.ID); !errors.Is(err, api.ErrQuotaExceeded) {
		t.Fatalf("claim at max_servers 0 = %v, want ErrQuotaExceeded", err)
	}
@@ -618,9 +615,6 @@ func TestSetQuotasReplacesEveryCap(t *testing.T) {
	gate(false)
	set(api.QuotaInput{}, "-/-/-/-")
	gate(true)
	if ok, err := repo.QuotaAvailable(ctx, u.ID); err != nil || !ok {
		t.Fatalf("QuotaAvailable with every cap lifted = %v, %v; want true", ok, err)
	}
	if claimed, err := repo.ClaimServer(ctx, name, u.ID); err != nil || !claimed {
		t.Fatalf("claim with every cap lifted = %v, %v; want claimed", claimed, err)
	}