All checks were successful
CD / Build and push images (push) Successful in 3m17s
CI / Lint, typecheck, test (push) Successful in 2m34s
CI / Auth e2e pack (push) Successful in 3m27s
CI / Build container images (push) Has been skipped
CD / Deploy to Test (push) Successful in 8s
CD / Smoke tests against Test (push) Successful in 1m14s
CD / Promote to Int (push) Successful in 11s
Site Admins tune quotas per user and per pond on the three-level ladder (pond override → user override → instance default, data-model.md §Quotas). - api `admin/`: a `QuotaAdminService` + Site-Admin-gated endpoints under `/admin/quotas` — look up a user (username/e-mail) or pond (slug), list every quota's override / instance default / effective value (resolved through the existing QuotaService, the single consumption path, so a change takes effect immediately) plus current usage, and set/clear a per-subject override. Every change is audit-logged. A pond's effective values resolve on its own override then its owner's, matching the consumption checks. - web: the Admin area gains a 'Quotas' surface — the instance defaults move into a proper number-input form (was raw settings, #19), and a per-subject panel looks a user/pond up, shows the ladder with usage, flags subjects over their effective limit, and sets/clears overrides. New `quotas` i18n namespace (de+en). - tests: `quota-admin.e2e.db.test.ts` (override → effective changes at once and QuotaService sees it; clear → falls back to the default; lookup; Site-Admin gating); a browser `admin-quotas` pack proving an override raised in the UI immediately lets a user create another shared pond (issue #22 consumption). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01EwZ4jR4KFAPvpjWevfUGX1
46 lines
1.4 KiB
TypeScript
46 lines
1.4 KiB
TypeScript
/**
|
|
* Quota dimensions (ADR 0011, issue #22). Each value resolves on the
|
|
* three-level ladder: pond override → user override → instance default —
|
|
* the most specific wins, mirroring the permission philosophy.
|
|
*/
|
|
import { z } from 'zod';
|
|
|
|
export const QUOTA_KEYS = [
|
|
'editors_per_pond',
|
|
'readers_per_pond',
|
|
'additional_ponds',
|
|
'storage_bytes',
|
|
'max_file_bytes',
|
|
] as const;
|
|
|
|
export type QuotaKey = (typeof QUOTA_KEYS)[number];
|
|
|
|
/** Which subject a quota override applies to (data-model.md §Quotas). */
|
|
export type QuotaSubject = 'user' | 'pond';
|
|
|
|
/**
|
|
* One quota dimension for a subject in the admin UI (issue #58): the effective
|
|
* value on the ladder (pond/user override → instance default), the override at
|
|
* this level if any, and current usage where the dimension is metered.
|
|
*/
|
|
export interface QuotaLineView {
|
|
key: QuotaKey;
|
|
/** The override set at this subject level, or `null` (falls back). */
|
|
override: number | null;
|
|
instanceDefault: number;
|
|
effective: number;
|
|
/** Current usage for metered dimensions (storage/seats/ponds), else `null`. */
|
|
usage: number | null;
|
|
}
|
|
|
|
/** The resolved quota picture for one user or pond. */
|
|
export interface QuotaSubjectView {
|
|
type: QuotaSubject;
|
|
id: string;
|
|
label: string;
|
|
lines: QuotaLineView[];
|
|
}
|
|
|
|
export const setQuotaOverrideSchema = z.object({ value: z.number().int().min(0) });
|
|
export type SetQuotaOverrideInput = z.infer<typeof setQuotaOverrideSchema>;
|