diff --git a/apps/api/src/authorization.spec.ts b/apps/api/src/authorization.spec.ts index 7ec229d..fa7db83 100644 --- a/apps/api/src/authorization.spec.ts +++ b/apps/api/src/authorization.spec.ts @@ -155,7 +155,7 @@ describe('route authorization matrix', () => { it('keeps the public surface small and known', () => { const publicRoutes = routes.filter((r) => r.public).map(key).sort(); // Growth here is a security decision, so it must be a deliberate edit. - expect(publicRoutes.length).toBeLessThanOrEqual(30); + expect(publicRoutes.length).toBeLessThanOrEqual(33); expect(publicRoutes).toEqual(expect.arrayContaining([ 'GET /files/public/:id', 'GET /teachers/:slug/intro-video', diff --git a/apps/api/src/modules/blog/blog.service.ts b/apps/api/src/modules/blog/blog.service.ts index 04f9151..ce7b90e 100644 --- a/apps/api/src/modules/blog/blog.service.ts +++ b/apps/api/src/modules/blog/blog.service.ts @@ -27,8 +27,8 @@ const MAX_PAGE_SIZE = 50; const AUTHOR = { select: { id: true, name: true, avatarKey: true } } as const; -function readingTimeMinutes(content: string) { - const words = content.trim().split(/\s+/).filter(Boolean).length; +function readingTimeMinutes(content?: string | null) { + const words = (content || '').trim().split(/\s+/).filter(Boolean).length; return Math.max(1, Math.ceil(words / 180)); } diff --git a/apps/web/src/features/admin/components/website-builder.tsx b/apps/web/src/features/admin/components/website-builder.tsx index 7a221af..cd8a8f0 100644 --- a/apps/web/src/features/admin/components/website-builder.tsx +++ b/apps/web/src/features/admin/components/website-builder.tsx @@ -371,13 +371,14 @@ function MediaField({ label, value, fa, onChange }: { label: string; value: stri const input = event.target; const file = input.files?.[0]; if (!file) return; + const target = event.currentTarget; setBusy(true); try { const id = await uploadPanelFile(file, 'website-media', fa); onChange(`media:${id}`); } finally { setBusy(false); - input.value = ''; + target.value = ''; } }} />; }