mirror of
https://github.com/MBombeck/HealthLog.git
synced 2026-11-09 04:50:59 +00:00
feat(admin): expose the two document-vault limits
The admin settings GET/PUT carry documentMaxFileBytes (1 MiB – 100 MiB; the hard ceiling exists because single-shot GCM + bounded in-memory reads are load-bearing past it, and the upload-time resolver clamps again as defence-in-depth) and documentQuotaBytes (10 MiB – 1 TiB instance default). The admin user PUT accepts a nullable per-user quota override; null clears it back to the instance default. Cookie-only requireAdmin() guards both surfaces as before — Bearer tokens cannot reach them. Task: W1-T13.
This commit is contained in:
1 parent
de9e528614
commit
155b74ccf4
3 files changed
+62
-1
No files matched your search
@@ -51,6 +51,13 @@ export const GET = apiHandler(async () => {
|
||||
reminderLateMinutes: settings?.reminderLateMinutes ?? 120,
|
||||
reminderMissedMinutes: settings?.reminderMissedMinutes ?? 240,
|
||||
moodLogGlobal: settings?.moodLogGlobal ?? true,
|
||||
// Document vault limits (per-file cap + per-user quota default). BigInt
|
||||
// column → Number for the JSON envelope (values are far below 2^53).
|
||||
documentMaxFileBytes: settings?.documentMaxFileBytes ?? 26_214_400,
|
||||
documentQuotaBytes:
|
||||
settings?.documentQuotaBytes !== undefined
|
||||
? Number(settings.documentQuotaBytes)
|
||||
: 1_073_741_824,
|
||||
// v1.4.25 W7 — null means "fall back to Europe/Berlin in the
|
||||
// resolver"; surfacing the raw value lets the admin UI render
|
||||
// an empty picker placeholder until they opt in.
|
||||
@@ -176,6 +183,17 @@ export const PUT = apiHandler(async (request: NextRequest) => {
|
||||
auditDetails.reminderMissedMinutes = data.reminderMissedMinutes;
|
||||
}
|
||||
|
||||
// Document vault limits (schema already bounds both; the upload-time
|
||||
// resolver clamps the cap to the hard ceiling again as defence-in-depth).
|
||||
if (data.documentMaxFileBytes !== undefined) {
|
||||
updates.documentMaxFileBytes = data.documentMaxFileBytes;
|
||||
auditDetails.documentMaxFileBytes = data.documentMaxFileBytes;
|
||||
}
|
||||
if (data.documentQuotaBytes !== undefined) {
|
||||
updates.documentQuotaBytes = BigInt(data.documentQuotaBytes);
|
||||
auditDetails.documentQuotaBytes = data.documentQuotaBytes;
|
||||
}
|
||||
|
||||
// v1.4.25 W7 — server-default timezone for new signups.
|
||||
// Empty string clears the override (resolver falls back to
|
||||
// Europe/Berlin); a non-empty string must pass Intl validation
|
||||
@@ -240,6 +258,8 @@ export const PUT = apiHandler(async (request: NextRequest) => {
|
||||
reminderLateMinutes: settings.reminderLateMinutes,
|
||||
reminderMissedMinutes: settings.reminderMissedMinutes,
|
||||
moodLogGlobal: settings.moodLogGlobal,
|
||||
documentMaxFileBytes: settings.documentMaxFileBytes,
|
||||
documentQuotaBytes: Number(settings.documentQuotaBytes),
|
||||
defaultUserTimezone: settings.defaultUserTimezone,
|
||||
});
|
||||
});
|
||||
@@ -19,6 +19,16 @@ const updateUserSchema = z.object({
|
||||
// v1.23 — per-user "require a second factor" override. Effective requirement
|
||||
// is the OR of this and the instance-wide AppSettings.mfaRequired policy.
|
||||
mfaEnforced: z.boolean().optional(),
|
||||
// Document vault — per-user storage-quota override in bytes. Null clears
|
||||
// the override (the instance default applies again). Same bounds as the
|
||||
// instance-wide setting.
|
||||
documentQuotaBytes: z
|
||||
.number()
|
||||
.int()
|
||||
.min(10_485_760)
|
||||
.max(1_099_511_627_776)
|
||||
.nullable()
|
||||
.optional(),
|
||||
});
|
||||
|
||||
export const PUT = apiHandler(
|
||||
@@ -70,6 +80,12 @@ export const PUT = apiHandler(
|
||||
...(parsed.data.mfaEnforced !== undefined && {
|
||||
mfaEnforced: parsed.data.mfaEnforced,
|
||||
}),
|
||||
...(parsed.data.documentQuotaBytes !== undefined && {
|
||||
documentQuotaBytes:
|
||||
parsed.data.documentQuotaBytes === null
|
||||
? null
|
||||
: BigInt(parsed.data.documentQuotaBytes),
|
||||
}),
|
||||
},
|
||||
select: {
|
||||
id: true,
|
||||
@@ -78,6 +94,7 @@ export const PUT = apiHandler(
|
||||
role: true,
|
||||
createdAt: true,
|
||||
mfaEnforced: true,
|
||||
documentQuotaBytes: true,
|
||||
},
|
||||
});
|
||||
|
||||
@@ -87,6 +104,13 @@ export const PUT = apiHandler(
|
||||
details: { targetUserId: id, changes: parsed.data },
|
||||
});
|
||||
|
||||
return apiSuccess(updatedUser);
|
||||
return apiSuccess({
|
||||
...updatedUser,
|
||||
// BigInt → Number for the JSON envelope; null = no override.
|
||||
documentQuotaBytes:
|
||||
updatedUser.documentQuotaBytes === null
|
||||
? null
|
||||
: Number(updatedUser.documentQuotaBytes),
|
||||
});
|
||||
},
|
||||
);
|
||||
@@ -67,6 +67,23 @@ export const adminSettingsSchema = z
|
||||
reminderLateMinutes: z.number().int().min(15).max(480).optional(),
|
||||
reminderMissedMinutes: z.number().int().min(30).max(720).optional(),
|
||||
moodLogGlobal: z.boolean().optional(),
|
||||
// Document vault — the two admin-tunable limits. The per-file cap is
|
||||
// bounded by the hard 100 MiB ceiling (single-shot GCM + bounded
|
||||
// in-memory reads are load-bearing past that; the runtime resolver
|
||||
// clamps again as defence-in-depth). The quota accepts up to 1 TiB —
|
||||
// BYTEA rides pg_dump, so anything larger is an operator error.
|
||||
documentMaxFileBytes: z
|
||||
.number()
|
||||
.int()
|
||||
.min(1_048_576)
|
||||
.max(104_857_600)
|
||||
.optional(),
|
||||
documentQuotaBytes: z
|
||||
.number()
|
||||
.int()
|
||||
.min(10_485_760)
|
||||
.max(1_099_511_627_776)
|
||||
.optional(),
|
||||
// v1.4.25 W7 — server-wide default timezone for new signups
|
||||
// that don't carry a browser-detected zone. The route validates
|
||||
// against Intl.supportedValuesOf at runtime; this schema just
|
||||
|
||||
Reference in new issue
Block a user