Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
2935f43
Suppress error logs in test output
kevmodrome Oct 28, 2025
a31b360
Fix protected route in BasePage playwright test class
kevmodrome Oct 28, 2025
6565345
Fix some type errors in the content and tag services
kevmodrome Oct 28, 2025
75b9df7
Fix LLM API and Svelte 5 binding type errors
kevmodrome Oct 28, 2025
2764b13
Fix component prop and implicit any type errors
kevmodrome Oct 28, 2025
466554f
Revert 'Fix component prop and implicit any type errors' - using any …
kevmodrome Oct 28, 2025
7f8d848
Fix additional type errors in forms, services, and tests
kevmodrome Oct 28, 2025
eeea5bd
Fix external content service and type system errors
kevmodrome Oct 28, 2025
92fca3a
Fix content service return types and external content updates
kevmodrome Oct 28, 2025
00a355b
Fix external content addContent and test date handling
kevmodrome Oct 28, 2025
f192564
Fix database import and generic type constraints
kevmodrome Oct 28, 2025
79c9ff1
Fix test content objects to match discriminated union schemas
kevmodrome Oct 28, 2025
78f4c89
Add environment variable type declarations and fix db import
kevmodrome Oct 28, 2025
df671b1
Remove unused code and fix ContentCard component types
kevmodrome Oct 28, 2025
a747b7e
Fix test mock fetch errors and improve metadata test data
kevmodrome Oct 28, 2025
e46e642
Fix remaining test type errors
kevmodrome Oct 28, 2025
29e6be0
Remove unused handleFormAction and fix admin content body access
kevmodrome Oct 28, 2025
f7102b3
Fix property access on union types in admin content route
kevmodrome Oct 28, 2025
426defb
Fix moderation route type errors
kevmodrome Oct 28, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 5 additions & 5 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,11 +17,11 @@
"db:test:init": "NODE_ENV=test bun run scripts/test-db-init.ts",
"db:test:seed": "NODE_ENV=test bun run scripts/test-db-seed.ts",
"test:integration": "rm -f test-*.db* && NODE_ENV=test bun run db:test:init && bun run db:test:seed && playwright test",
"test": "bun test src/",
"test:watch": "bun test src/ --watch",
"test:coverage": "bun test src/ --coverage",
"test:coverage:lcov": "bun test src/ --coverage --coverage-reporter=lcov",
"test:ci": "bun test src/ --coverage --coverage-reporter=lcov"
"test": "bun test src/ --preload ./src/test-setup.ts",
"test:watch": "bun test src/ --watch --preload ./src/test-setup.ts",
"test:coverage": "bun test src/ --coverage --preload ./src/test-setup.ts",
"test:coverage:lcov": "bun test src/ --coverage --coverage-reporter=lcov --preload ./src/test-setup.ts",
"test:ci": "bun test src/ --coverage --coverage-reporter=lcov --preload ./src/test-setup.ts"
},
"devDependencies": {
"@ai-sdk/anthropic": "^2.0.38",
Expand Down
13 changes: 13 additions & 0 deletions src/app.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,4 +42,17 @@ declare global {
}
}

// Declare environment variables
declare module '$env/static/private' {
export const DB_PATH: string
export const GITHUB_CLIENT_ID: string
export const GITHUB_CLIENT_SECRET: string
export const GITHUB_AUTHORIZATION_CALLBACK_URL: string
export const ANTHROPIC_API_KEY: string | undefined
export const YOUTUBE_API_KEY: string | undefined
export const GITHUB_TOKEN: string | undefined
export const BULK_IMPORT_API_KEY: string | undefined
export const SEED_DATABASE: string | undefined
}

export {}
3 changes: 1 addition & 2 deletions src/lib/admin/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -96,8 +96,7 @@ export const CONTENT_TYPE_ICONS: Record<ContentType, string> = {
video: 'video',
library: 'package',
announcement: 'megaphone',
collection: 'folder',
event: 'calendar'
collection: 'folder'
} as const

// Common action types
Expand Down
55 changes: 0 additions & 55 deletions src/lib/admin/utils.ts
Original file line number Diff line number Diff line change
@@ -1,58 +1,3 @@
import { fail, redirect } from '@sveltejs/kit'
import { superValidate, message } from 'sveltekit-superforms'
import { zod4 } from 'sveltekit-superforms/adapters'
import type { z } from 'zod/v4'

/**
* Common pattern for handling form submissions in admin pages
*/
export async function handleFormAction<T extends z.ZodSchema>({
request,
schema,
onSuccess,
successMessage,
redirectTo,
errorMessage = 'Operation failed. Please try again.'
}: {
request: Request
schema: T
onSuccess: (data: z.infer<T>) => Promise<void> | void
successMessage?: string
redirectTo?: string
errorMessage?: string
}) {
const form = await superValidate(request, zod4(schema))

if (!form.valid) {
return fail(400, { form })
}

try {
await onSuccess(form.data)

if (successMessage) {
message(form, { type: 'success', text: successMessage })
}
} catch (error) {
console.error('Form action error:', error)

const errorText = error instanceof Error ? error.message : errorMessage

message(form, { type: 'error', text: errorText })
if (successMessage) {
return fail(500, { form })
} else {
return fail(500, { form, error: errorText })
}
}

if (redirectTo) {
redirect(303, redirectTo)
}

return { form }
}

/**
* Generate a URL-safe slug from a string
*/
Expand Down
9 changes: 8 additions & 1 deletion src/lib/schema/content.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,23 @@
import { z } from 'zod/v4'
import { tagSchema } from './tags'

export const typeSchema = z.enum(['video', 'library', 'announcement', 'collection', 'recipe'])
export const statusSchema = z.enum(['draft', 'published', 'archived'])

// For form submissions, we accept either tag IDs (strings) or full tag objects
const tagInputSchema = z.union([
z.string(), // Tag ID
tagSchema // Full tag object
])

const baseContentSchema = z.object({
id: z.string(),
title: z.string().min(1, 'Title is required'),
slug: z.string().min(1, 'Slug is required'),
description: z.string().min(1, 'Description is required'),
status: statusSchema,
type: typeSchema,
tags: z.array(z.string()).min(1, 'At least one tag is required'),
tags: z.array(tagInputSchema).min(1, 'At least one tag is required'),
author_id: z.string().optional(),
created_at: z.string(),
updated_at: z.string(),
Expand Down
5 changes: 3 additions & 2 deletions src/lib/server/db/utils.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
import { db } from './initiate'
import type { Database } from 'bun:sqlite'

/**
* Checks if the database has any data in the content table.
* @param db - The database instance to check
* @returns {boolean} True if data exists, false otherwise
*/
export function hasData(): boolean {
export function hasData(db: Database): boolean {
try {
const result = db.prepare('SELECT COUNT(*) as count FROM content').get() as { count: number }
return result.count > 0
Expand Down
2 changes: 1 addition & 1 deletion src/lib/server/services/AnnouncementService.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import type { Database } from 'better-sqlite3'
import type { Database } from 'bun:sqlite'

export interface PlacementLocation {
id: string
Expand Down
38 changes: 28 additions & 10 deletions src/lib/server/services/content.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -107,18 +107,18 @@ describe('ContentService', () => {
test('should sort by latest by default', () => {
const content = contentService.getFilteredContent()
for (let i = 1; i < content.length; i++) {
expect(new Date(content[i].published_at) <= new Date(content[i - 1].published_at)).toBe(
true
)
const currentDate = content[i].published_at ? new Date(content[i].published_at) : new Date(0)
const previousDate = content[i - 1].published_at ? new Date(content[i - 1].published_at) : new Date(0)
expect(currentDate <= previousDate).toBe(true)
}
})

test('should sort by oldest when specified', () => {
const content = contentService.getFilteredContent({ sort: 'oldest' })
for (let i = 1; i < content.length; i++) {
expect(new Date(content[i].published_at) >= new Date(content[i - 1].published_at)).toBe(
true
)
const currentDate = content[i].published_at ? new Date(content[i].published_at) : new Date(0)
const previousDate = content[i - 1].published_at ? new Date(content[i - 1].published_at) : new Date(0)
expect(currentDate >= previousDate).toBe(true)
}
})
})
Expand Down Expand Up @@ -202,8 +202,10 @@ describe('ContentService', () => {
slug: 'new-test-content',
type: 'recipe' as const,
body: 'Test body',
rendered_body: '<p>Test body</p>',
description: 'Test description',
status: 'draft' as const,
published_at: null,
tags: ['tag1']
}

Expand Down Expand Up @@ -232,8 +234,10 @@ describe('ContentService', () => {
slug: 'content-with-author',
type: 'recipe' as const,
body: 'Test body',
rendered_body: '<p>Test body</p>',
description: 'Test description',
status: 'published' as const,
published_at: new Date().toISOString(),
tags: []
}

Expand All @@ -253,9 +257,10 @@ describe('ContentService', () => {
title: 'Multi-tag Content',
slug: 'multi-tag-content',
type: 'library' as const,
body: 'Test body',
description: 'Test description',
status: 'published' as const,
published_at: new Date().toISOString(),
metadata: {},
tags: ['tag1', 'tag2']
}

Expand All @@ -275,8 +280,10 @@ describe('ContentService', () => {
slug: 'searchable-content',
type: 'recipe' as const,
body: 'Content with searchable tags',
rendered_body: '<p>Content with searchable tags</p>',
description: 'Description',
status: 'published' as const,
published_at: new Date().toISOString(),
tags: ['tag1', 'tag2']
}

Expand All @@ -302,7 +309,10 @@ describe('ContentService', () => {
type: existing.type,
status: existing.status,
body: existing.body,
description: 'Updated description'
rendered_body: existing.rendered_body || '',
published_at: existing.published_at,
description: 'Updated description',
tags: []
}

contentService.updateContent(updates)
Expand All @@ -322,8 +332,11 @@ describe('ContentService', () => {
slug: existing.slug,
type: existing.type,
body: existing.body,
rendered_body: existing.rendered_body || '',
published_at: existing.published_at,
description: existing.description,
status: 'published' as const
status: 'published' as const,
tags: []
}

contentService.updateContent(updates)
Expand All @@ -343,6 +356,8 @@ describe('ContentService', () => {
type: existing.type,
status: existing.status,
body: existing.body,
rendered_body: existing.rendered_body || '',
published_at: existing.published_at,
description: existing.description,
tags: ['tag2', 'tag3']
}
Expand All @@ -365,8 +380,11 @@ describe('ContentService', () => {
slug: existing.slug,
type: existing.type,
body: existing.body,
rendered_body: existing.rendered_body || '',
published_at: existing.published_at,
description: existing.description,
status: 'published' as const
status: 'published' as const,
tags: []
}

contentService.updateContent(updates)
Expand Down
Loading
Loading