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
71 lines
2.0 KiB
TypeScript
71 lines
2.0 KiB
TypeScript
import {
|
|
BadRequestException,
|
|
Body,
|
|
Controller,
|
|
Delete,
|
|
Get,
|
|
Param,
|
|
Put,
|
|
Query,
|
|
Req,
|
|
UseGuards,
|
|
} from '@nestjs/common';
|
|
import {
|
|
QuotaSubject,
|
|
QuotaSubjectView,
|
|
SetQuotaOverrideInput,
|
|
setQuotaOverrideSchema,
|
|
} from '@dorfteich/shared';
|
|
|
|
import { AuthedRequest } from '../auth/auth.guard';
|
|
import { ZodValidationPipe } from '../common/zod-validation.pipe';
|
|
import { SiteAdminGuard } from './site-admin.guard';
|
|
import { QuotaAdminService } from './quota-admin.service';
|
|
|
|
function asSubject(type: string): QuotaSubject {
|
|
if (type !== 'user' && type !== 'pond') throw new BadRequestException({ code: 'bad_request' });
|
|
return type;
|
|
}
|
|
|
|
/** Site-Admin quota override management (issue #58). */
|
|
@Controller('admin/quotas')
|
|
@UseGuards(SiteAdminGuard)
|
|
export class QuotaAdminController {
|
|
constructor(private readonly quotaAdmin: QuotaAdminService) {}
|
|
|
|
/** Resolve a user (username/e-mail) or pond (slug) to id + label. */
|
|
@Get('lookup')
|
|
async lookup(
|
|
@Query('type') type: string,
|
|
@Query('q') q: string,
|
|
): Promise<{ id: string; label: string }> {
|
|
return this.quotaAdmin.lookup(asSubject(type), q ?? '');
|
|
}
|
|
|
|
@Get(':type/:id')
|
|
async subject(@Param('type') type: string, @Param('id') id: string): Promise<QuotaSubjectView> {
|
|
return this.quotaAdmin.subject(asSubject(type), id);
|
|
}
|
|
|
|
@Put(':type/:id/:key')
|
|
async set(
|
|
@Param('type') type: string,
|
|
@Param('id') id: string,
|
|
@Param('key') key: string,
|
|
@Body(new ZodValidationPipe(setQuotaOverrideSchema)) input: SetQuotaOverrideInput,
|
|
@Req() request: AuthedRequest,
|
|
): Promise<QuotaSubjectView> {
|
|
return this.quotaAdmin.setOverride(request.user!, asSubject(type), id, key, input.value);
|
|
}
|
|
|
|
@Delete(':type/:id/:key')
|
|
async clear(
|
|
@Param('type') type: string,
|
|
@Param('id') id: string,
|
|
@Param('key') key: string,
|
|
@Req() request: AuthedRequest,
|
|
): Promise<QuotaSubjectView> {
|
|
return this.quotaAdmin.clearOverride(request.user!, asSubject(type), id, key);
|
|
}
|
|
}
|