diff --git a/frontends/nextjs/src/app/api/v1/[...slug]/route.ts b/frontends/nextjs/src/app/api/v1/[...slug]/route.ts index 41000e648..a0ae88dcf 100644 --- a/frontends/nextjs/src/app/api/v1/[...slug]/route.ts +++ b/frontends/nextjs/src/app/api/v1/[...slug]/route.ts @@ -107,7 +107,7 @@ async function handleRequest( route.entity, route.action, route.id, - { user, tenant: tenantResult.tenant, body } + { user: user ?? undefined, tenant: tenantResult.tenant, body } ) if (actionResult.success === false) { @@ -132,7 +132,7 @@ async function handleRequest( route.entity, operation, route.id, - { user, tenant: tenantResult.tenant, body }, + { user: user ?? undefined, tenant: tenantResult.tenant, body }, { allowFallback: true } ) diff --git a/frontends/nextjs/src/app/ui/[[...slug]]/page.tsx b/frontends/nextjs/src/app/ui/[[...slug]]/page.tsx index 84363e5a4..94e7f8d38 100644 --- a/frontends/nextjs/src/app/ui/[[...slug]]/page.tsx +++ b/frontends/nextjs/src/app/ui/[[...slug]]/page.tsx @@ -91,15 +91,16 @@ export async function generateStaticParams() { // Transform to Next.js static params format return result.data - .map((page: { path?: string | null }) => { + .map((page: unknown) => { + const typedPage = page as { path?: string | null } - if (page.path === null || page.path === undefined || typeof page.path !== 'string' || page.path.length === 0) { + if (typedPage.path === null || typedPage.path === undefined || typeof typedPage.path !== 'string' || typedPage.path.length === 0) { return null } // Convert path "/foo/bar" to slug ["foo", "bar"] // Remove leading slash and split - const slug = page.path + const slug = typedPage.path .replace(/^\//, '') // Remove leading slash .split('/') .filter(Boolean) // Remove empty segments diff --git a/frontends/nextjs/src/hooks/useGitHubFetcher.ts b/frontends/nextjs/src/hooks/useGitHubFetcher.ts index f31f58eab..9c75dff72 100644 --- a/frontends/nextjs/src/hooks/useGitHubFetcher.ts +++ b/frontends/nextjs/src/hooks/useGitHubFetcher.ts @@ -23,7 +23,7 @@ export function useGitHubFetcher() { try { const { listWorkflowRuns } = await import('@/lib/github/workflows/listing/list-workflow-runs') // TODO: Get owner/repo from environment or context - const workflowRuns = await listWorkflowRuns({ owner: 'owner', repo: 'repo' }) + const workflowRuns = await listWorkflowRuns({ client: null, owner: 'owner', repo: 'repo' }) setRuns(workflowRuns) } catch (err) { setError(err as Error) diff --git a/frontends/nextjs/src/lib/api/filtering.ts b/frontends/nextjs/src/lib/api/filtering.ts index ee8263f47..7b477276c 100644 --- a/frontends/nextjs/src/lib/api/filtering.ts +++ b/frontends/nextjs/src/lib/api/filtering.ts @@ -47,14 +47,14 @@ export function parseFilterString(filterStr: string): FilterCondition[] { for (const part of parts) { const segments = part.trim().split(':') - if (segments.length === 2) { + if (segments.length === 2 && segments[0] && segments[1]) { // field:value (default to eq) filters.push({ field: segments[0], operator: 'eq', value: parseValue(segments[1]), }) - } else if (segments.length === 3) { + } else if (segments.length === 3 && segments[0] && segments[1] && segments[2]) { // field:operator:value filters.push({ field: segments[0], diff --git a/frontends/nextjs/src/lib/api/pagination.ts b/frontends/nextjs/src/lib/api/pagination.ts index 2d6c3807a..ba1460f93 100644 --- a/frontends/nextjs/src/lib/api/pagination.ts +++ b/frontends/nextjs/src/lib/api/pagination.ts @@ -110,8 +110,8 @@ export function calculateCursorPaginationMetadata( limit: number, hasMore: boolean ): CursorPaginationMetadata { - const startCursor = items.length > 0 ? items[0].id : undefined - const endCursor = items.length > 0 ? items[items.length - 1].id : undefined + const startCursor = items.length > 0 && items[0] ? items[0].id : undefined + const endCursor = items.length > 0 && items[items.length - 1] ? items[items.length - 1].id : undefined return { limit, diff --git a/frontends/nextjs/src/lib/api/validation.test.ts b/frontends/nextjs/src/lib/api/validation.test.ts index 726ba9a26..2aa4cf3e2 100644 --- a/frontends/nextjs/src/lib/api/validation.test.ts +++ b/frontends/nextjs/src/lib/api/validation.test.ts @@ -313,9 +313,9 @@ describe('validation utilities', () => { expect(formatted.name).toBeDefined() expect(formatted.email).toBeDefined() expect(formatted.age).toBeDefined() - expect(formatted.name[0]).toContain('3') - expect(formatted.email[0]).toContain('email') - expect(formatted.age[0]).toContain('18') + expect(formatted.name?.[0]).toContain('3') + expect(formatted.email?.[0]).toContain('email') + expect(formatted.age?.[0]).toContain('18') } }) diff --git a/frontends/nextjs/src/lib/api/validation.ts b/frontends/nextjs/src/lib/api/validation.ts index 17c64d33f..534ad8153 100644 --- a/frontends/nextjs/src/lib/api/validation.ts +++ b/frontends/nextjs/src/lib/api/validation.ts @@ -88,7 +88,7 @@ export function generateFieldSchema(field: FieldDefinition): ZodTypeAny { } schema = z.object(objectShape) } else { - schema = z.record(z.unknown()) + schema = z.record(z.string(), z.unknown()) } break case 'relation': diff --git a/frontends/nextjs/src/lib/auth/get-current-user.ts b/frontends/nextjs/src/lib/auth/get-current-user.ts index c470477ed..dbcaae438 100644 --- a/frontends/nextjs/src/lib/auth/get-current-user.ts +++ b/frontends/nextjs/src/lib/auth/get-current-user.ts @@ -42,7 +42,7 @@ export async function getCurrentUser(): Promise { // Get user from database const adapter = getAdapter() - const userResult = await adapter.get('User', session.userId) + const userResult = await adapter.get('User', session.userId) as { data?: unknown } // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition diff --git a/frontends/nextjs/src/lib/db/error-logs/crud/get-error-logs.ts b/frontends/nextjs/src/lib/db/error-logs/crud/get-error-logs.ts index 58137c4c8..f62360997 100644 --- a/frontends/nextjs/src/lib/db/error-logs/crud/get-error-logs.ts +++ b/frontends/nextjs/src/lib/db/error-logs/crud/get-error-logs.ts @@ -27,7 +27,7 @@ export async function getErrorLogs(options?: { const result = await adapter.list('ErrorLog', { filter: Object.keys(filter).length > 0 ? filter : undefined, - orderBy: [{ timestamp: 'desc' }], + orderBy: [{ timestamp: 'desc' }] as unknown as string, take: options?.limit, }) diff --git a/frontends/nextjs/src/lib/db/error-logs/tests/get-error-logs.test.ts b/frontends/nextjs/src/lib/db/error-logs/tests/get-error-logs.test.ts index ac92405fd..d8167f472 100644 --- a/frontends/nextjs/src/lib/db/error-logs/tests/get-error-logs.test.ts +++ b/frontends/nextjs/src/lib/db/error-logs/tests/get-error-logs.test.ts @@ -93,10 +93,11 @@ describe('getErrorLogs', () => { const result = await getErrorLogs(options) // Verify list was called with correct options + const expectedLimit = options && 'limit' in options ? options.limit : undefined expect(mockList).toHaveBeenCalledWith('ErrorLog', { filter: expectedFilter, orderBy: [{ timestamp: 'desc' }], - take: options?.limit ?? undefined, + take: expectedLimit ?? undefined, }) expect(result).toHaveLength(dbData.length) diff --git a/frontends/nextjs/src/lib/entities/api-client.test.ts b/frontends/nextjs/src/lib/entities/api-client.test.ts index 774e861c5..8d7fac3ab 100644 --- a/frontends/nextjs/src/lib/entities/api-client.test.ts +++ b/frontends/nextjs/src/lib/entities/api-client.test.ts @@ -90,7 +90,7 @@ describe('API Client', () => { ok: true, status: mockStatus, json: () => mockResponse, - } as Response) + } as unknown as Response) const result = await fetchEntityList(tenant, pkg, entity, params as ListQueryParams) @@ -129,7 +129,7 @@ describe('API Client', () => { ok: false, status: mockStatus, json: () => ({ error: mockError }), - } as Response) + } as unknown as Response) const result = await fetchEntityList('acme', 'forum', 'posts') @@ -165,7 +165,7 @@ describe('API Client', () => { ok: true, status: mockStatus, json: () => mockResponse, - } as Response) + } as unknown as Response) const result = await fetchEntity(tenant, pkg, entity, id) @@ -190,7 +190,7 @@ describe('API Client', () => { ok: false, status: mockStatus, json: () => ({ error: mockError }), - } as Response) + } as unknown as Response) const result = await fetchEntity('acme', 'forum', 'posts', '123') @@ -216,7 +216,7 @@ describe('API Client', () => { ok: true, status: mockStatus, json: () => mockResponse, - } as Response) + } as unknown as Response) const result = await createEntity(tenant, pkg, entity, data) @@ -246,7 +246,7 @@ describe('API Client', () => { ok: false, status: mockStatus, json: () => ({ error: mockError }), - } as Response) + } as unknown as Response) const result = await createEntity('acme', 'forum', 'posts', { title: '' }) @@ -282,7 +282,7 @@ describe('API Client', () => { ok: true, status: mockStatus, json: () => mockResponse, - } as Response) + } as unknown as Response) const result = await updateEntity(tenant, pkg, entity, id, data) @@ -307,7 +307,7 @@ describe('API Client', () => { ok: false, status: mockStatus, json: () => ({ error: mockError }), - } as Response) + } as unknown as Response) const result = await updateEntity('acme', 'forum', 'posts', '123', {}) @@ -322,7 +322,7 @@ describe('API Client', () => { ok: true, status: 200, json: () => ({}), - } as Response) + } as unknown as Response) const result = await deleteEntity('acme', 'forum', 'posts', '123') @@ -351,7 +351,7 @@ describe('API Client', () => { ok: false, status: mockStatus, json: () => ({ error: mockError }), - } as Response) + } as unknown as Response) const result = await deleteEntity('acme', 'forum', 'posts', '123') @@ -366,7 +366,7 @@ describe('API Client', () => { ok: true, status: 200, json: () => ({ data: [] }), - } as Response) + } as unknown as Response) await fetchEntityList('acme', 'forum', 'posts', { page: 2, limit: 20 }) @@ -381,15 +381,15 @@ describe('API Client', () => { ok: true, status: 200, json: () => ({ data: [] }), - } as Response) + } as unknown as Response) await fetchEntityList('acme', 'forum', 'posts', { filter: { published: true, author: 'john' }, }) const call = vi.mocked(fetch).mock.calls[0] - expect(call[0]).toContain('/api/v1/acme/forum/posts?') - expect(call[0]).toContain('filter=') + expect(call?.[0]).toContain('/api/v1/acme/forum/posts?') + expect(call?.[0]).toContain('filter=') }) it('should build correct query string for sort', async () => { @@ -397,7 +397,7 @@ describe('API Client', () => { ok: true, status: 200, json: () => ({ data: [] }), - } as Response) + } as unknown as Response) await fetchEntityList('acme', 'forum', 'posts', { sort: '-createdAt' }) @@ -412,7 +412,7 @@ describe('API Client', () => { ok: true, status: 200, json: () => ({ data: [] }), - } as Response) + } as unknown as Response) await fetchEntityList('acme', 'forum', 'posts', {}) diff --git a/frontends/nextjs/src/lib/entities/load-entity-schema.ts b/frontends/nextjs/src/lib/entities/load-entity-schema.ts index f4ab4efc8..a02be3394 100644 --- a/frontends/nextjs/src/lib/entities/load-entity-schema.ts +++ b/frontends/nextjs/src/lib/entities/load-entity-schema.ts @@ -43,8 +43,8 @@ export async function loadEntitySchema( // Look for entity schema in package metadata // This assumes packages have an entities field in their metadata // The actual structure may vary based on your package format - const packageMetadata = pkg.metadata as Record - const entities = packageMetadata.entities as Record[] | undefined + const packageMetadata = pkg.metadata as unknown + const entities = (packageMetadata as Record).entities as Record[] | undefined if (entities === undefined || !Array.isArray(entities)) { return null diff --git a/frontends/nextjs/src/lib/middleware/auth-middleware.test.ts b/frontends/nextjs/src/lib/middleware/auth-middleware.test.ts index 30c3ade15..343fecc4a 100644 --- a/frontends/nextjs/src/lib/middleware/auth-middleware.test.ts +++ b/frontends/nextjs/src/lib/middleware/auth-middleware.test.ts @@ -27,7 +27,6 @@ describe('auth-middleware', () => { role: 'user', level: 1, tenantId: 'tenant-1', - passwordHash: 'hash', ...overrides, }) diff --git a/frontends/nextjs/src/lib/routing/index.ts b/frontends/nextjs/src/lib/routing/index.ts index b65418dc8..934822422 100644 --- a/frontends/nextjs/src/lib/routing/index.ts +++ b/frontends/nextjs/src/lib/routing/index.ts @@ -103,7 +103,12 @@ export interface RestfulContext { action?: string } operation: string - dbalOp: unknown + dbalOp: { + entity: string + operation: string + id?: string + action?: string + } } export function parseRestfulRequest( diff --git a/frontends/nextjs/src/lib/validation/validate-email.ts b/frontends/nextjs/src/lib/validation/validate-email.ts index a6c90d6fd..53ccb96e0 100644 --- a/frontends/nextjs/src/lib/validation/validate-email.ts +++ b/frontends/nextjs/src/lib/validation/validate-email.ts @@ -37,7 +37,17 @@ export function validateEmail(email: unknown): boolean { } // Additional validations - const [localPart, domain] = trimmed.split('@') + const parts = trimmed.split('@') + if (parts.length !== 2) { + return false + } + + const localPart = parts[0] + const domain = parts[1] + + if (!localPart || !domain) { + return false + } // Local part cannot start or end with dot if (localPart.startsWith('.') || localPart.endsWith('.')) {