diff --git a/bun.lockb b/bun.lockb index 0a2a8a4..01e0c54 100755 Binary files a/bun.lockb and b/bun.lockb differ diff --git a/src/lib/components/PostFilters.svelte b/src/lib/components/PostFilters.svelte index 43f9268..5043e6b 100644 --- a/src/lib/components/PostFilters.svelte +++ b/src/lib/components/PostFilters.svelte @@ -19,17 +19,17 @@ const keywordData = $derived({ table: [ - ...posts.flatMap((post) => + ...posts.map((post) => post.keyword.map((keyword) => { return { title: keyword.title, filtered: false }; }) ), - ...filteredPosts.flatMap((post) => + ...filteredPosts.map((post) => post.keyword.map((keyword) => { return { title: keyword.title, filtered: true }; }) ) - ] + ].flat() }); const educationData = $derived({ diff --git a/src/lib/components/ui/MultiSelect.svelte b/src/lib/components/ui/MultiSelect.svelte index d28e059..b765a48 100644 --- a/src/lib/components/ui/MultiSelect.svelte +++ b/src/lib/components/ui/MultiSelect.svelte @@ -23,8 +23,6 @@ let detailsOpen = $state(false); - let valuesSet = $derived(new Set(values)); - function updateValue(optionValue: string) { if (values.includes(optionValue)) { values = values.filter((v: string) => v !== optionValue); @@ -107,7 +105,7 @@ updateValue(option.value)} /> diff --git a/src/routes/api/newsletter/daily-digest/+server.ts b/src/routes/api/newsletter/daily-digest/+server.ts index 4ad303d..e31fdb5 100644 --- a/src/routes/api/newsletter/daily-digest/+server.ts +++ b/src/routes/api/newsletter/daily-digest/+server.ts @@ -47,18 +47,21 @@ export const POST: RequestHandler = async ({ locals: { supabase }, request }) => // Common email body parts const textBodyHeader = `Here are the new positions posted on vispositions in the last 24 hours:\n\n`; const htmlBodyHeader = `

Here are the new positions posted on visPositions in the last 24 hours:

`; + const postsText = posts + .map( + (post) => + `- ${post.title}\n ${post.description?.substring(0, 100)}...\n View: ${siteUrl}/jobs/${post.id}\n\n` + ) + .join(''); + const linkedinText = posts.map((post) => `- ${post.title}\n`).join(''); + const postsHtml = + posts + .map((post) => { + const safeTitle = escapeHtml(post.title); + const safeDesc = post.description ? escapeHtml(post.description.substring(0, 100)) : ''; + return `
  • ${safeTitle}
    ${safeDesc}...
  • `; + }) + .join('') + ``; const textBody = `${textBodyHeader}${postsText}` + diff --git a/src/routes/api/post/[id]/+server.ts b/src/routes/api/post/[id]/+server.ts index 5b13f67..ff095b0 100644 --- a/src/routes/api/post/[id]/+server.ts +++ b/src/routes/api/post/[id]/+server.ts @@ -34,18 +34,6 @@ export const PATCH = async ({ locals: { supabase, safeGetSession }, params, requ const data = await request.json(); - if ( - (data.title !== undefined && typeof data.title !== 'string') || - (data.description !== undefined && typeof data.description !== 'string') || - (data.contact !== undefined && typeof data.contact !== 'string') || - (data.industry !== undefined && typeof data.industry !== 'boolean') || - (data.education !== undefined && typeof data.education !== 'string') || - (data.expiration_date !== undefined && typeof data.expiration_date !== 'string') || - (data.keywords !== undefined && !Array.isArray(data.keywords)) - ) { - throw error(400, 'Invalid input data'); - } - // Start a transaction to update both post and keywords // Always include expiration_date in the update const updateData = { diff --git a/src/routes/api/post/[id]/approve/+server.ts b/src/routes/api/post/[id]/approve/+server.ts index 21a90f4..6a057c6 100644 --- a/src/routes/api/post/[id]/approve/+server.ts +++ b/src/routes/api/post/[id]/approve/+server.ts @@ -1,6 +1,5 @@ import { ADMIN_EMAIL } from '$env/static/private'; -import { error, json } from '@sveltejs/kit'; -import type { RequestHandler } from './$types'; +import { error, json, type RequestHandler } from '@sveltejs/kit'; export const PATCH: RequestHandler = async ({ locals: { supabase, safeGetSession }, params }) => { const { session } = await safeGetSession(); diff --git a/src/routes/api/post/[id]/server.test.ts b/src/routes/api/post/[id]/server.test.ts index 93058b2..1fd7a1f 100644 --- a/src/routes/api/post/[id]/server.test.ts +++ b/src/routes/api/post/[id]/server.test.ts @@ -12,65 +12,6 @@ vi.mock('@sveltejs/kit', () => ({ text: vi.fn((message) => new Response(message)) })); -describe('PATCH /api/post/[id]', () => { - it('should throw 400 when invalid input is provided', async () => { - const mockSafeGetSession = vi.fn().mockResolvedValue({ - session: { - user: { - email: 'test@example.com' - } - } - }); - - const locals = { - supabase: {}, - safeGetSession: mockSafeGetSession - }; - - const params = { - id: '1' - }; - - // Test invalid title - const request1 = { - json: vi.fn().mockResolvedValue({ - title: 123, - description: 'valid', - contact: 'valid', - industry: true, - education: 'none', - expiration_date: '2025-01-01', - keywords: [] - }) - }; - - const { PATCH } = await import('./+server'); - - await expect( - PATCH({ locals, params, request: request1 } as unknown as Parameters[0]) - ).rejects.toThrow('Invalid input data'); - - // Test invalid keywords array - const request2 = { - json: vi.fn().mockResolvedValue({ - title: 'valid', - description: 'valid', - contact: 'valid', - industry: true, - education: 'none', - expiration_date: '2025-01-01', - keywords: 'not-an-array' - }) - }; - - await expect( - PATCH({ locals, params, request: request2 } as unknown as Parameters[0]) - ).rejects.toThrow('Invalid input data'); - - expect(error).toHaveBeenCalledWith(400, 'Invalid input data'); - }); -}); - describe('DELETE /api/post/[id]', () => { it('should throw 404 when deleting a non-existent post', async () => { // Create a mock chain for supabase diff --git a/src/routes/api/webhooks/google-sheets/+server.ts b/src/routes/api/webhooks/google-sheets/+server.ts index db39059..ea88c86 100644 --- a/src/routes/api/webhooks/google-sheets/+server.ts +++ b/src/routes/api/webhooks/google-sheets/+server.ts @@ -137,6 +137,6 @@ export const POST = async ({ request }) => { return json({ success: true, insertedCount, skippedCount, errors }); } catch (error) { console.error('Webhook error:', error); - return json({ error: 'Internal Server Error', details: String(error) }, { status: 500 }); + return json({ error: 'Internal Server Error' }, { status: 500 }); } }; diff --git a/src/routes/api/webhooks/google-sheets/server.test.ts b/src/routes/api/webhooks/google-sheets/server.test.ts index 8c81f23..5a99dec 100644 --- a/src/routes/api/webhooks/google-sheets/server.test.ts +++ b/src/routes/api/webhooks/google-sheets/server.test.ts @@ -159,27 +159,23 @@ describe('Google Sheets Webhook API', () => { }); }); - it('should return 500 if an internal error occurs (catch block)', async () => { - const request = new Request('http://localhost/api/webhooks/google-sheets', { - method: 'POST', - headers: { Authorization: 'Bearer test_webhook_secret' } - }); - - // Force request.json to throw an error - request.json = vi.fn().mockRejectedValue(new Error('Simulated JSON parsing error')); - - // Suppress console.error during this test - const consoleSpy = vi.spyOn(console, 'error').mockImplementation(() => {}); + it('should return a generic 500 error without exposing internal details when an exception occurs', async () => { + // Create a mock request that will throw an exception during json() parsing + const request = { + headers: { + get: vi.fn().mockReturnValue('Bearer test_webhook_secret') + }, + json: vi.fn().mockRejectedValue(new Error('Sensitive database connection string exposed!')) + }; const response = await POST({ request } as unknown as Parameters[0]); const data = await response.json(); expect(response.status).toBe(500); expect(data).toEqual({ - error: 'Internal Server Error', - details: 'Error: Simulated JSON parsing error' + error: 'Internal Server Error' }); - - consoleSpy.mockRestore(); + // Ensure that the detailed error message is not present in the response + expect(data.details).toBeUndefined(); }); }); diff --git a/src/routes/api/webhooks/post/server.test.ts b/src/routes/api/webhooks/post/server.test.ts index f2c06ac..084758a 100644 --- a/src/routes/api/webhooks/post/server.test.ts +++ b/src/routes/api/webhooks/post/server.test.ts @@ -65,28 +65,4 @@ describe('Post Webhook API', () => { expect(response.status).toBe(200); expect(data).toEqual({ success: true }); }); - - it('should return 500 and success: false if an error occurs during processing', async () => { - const request = new Request('http://localhost/api/webhooks/post', { - method: 'POST', - headers: { - Authorization: 'Bearer test_secret' - }, - body: JSON.stringify({ title: 'Test', description: 'Test desc' }) - }); - - // Mock request.json() to throw an error - request.json = vi.fn().mockRejectedValue(new Error('Simulated processing error')); - - const consoleErrorSpy = vi.spyOn(console, 'error').mockImplementation(() => {}); - - const response = await POST({ request } as unknown as Parameters[0]); - const data = await response.json(); - - expect(response.status).toBe(500); - expect(data).toEqual({ success: false }); - expect(consoleErrorSpy).toHaveBeenCalledWith('Webhook error:', expect.any(Error)); - - consoleErrorSpy.mockRestore(); - }); });