@structcms/api 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/index.ts","../src/storage/supabase-adapter.ts","../src/utils/sanitize.ts","../src/utils/slug.ts","../src/storage/handlers.ts","../src/media/resolve.ts","../src/delivery/handlers.ts","../src/media/types.ts","../src/media/supabase-adapter.ts","../src/media/handlers.ts","../src/supabase/factory.ts","../src/auth/supabase-adapter.ts","../src/auth/handlers.ts","../src/auth/middleware.ts","../src/export/handlers.ts"],"sourcesContent":["export type {\n Page,\n PageSection,\n PageFilter,\n CreatePageInput,\n UpdatePageInput,\n Navigation,\n NavigationItem,\n CreateNavigationInput,\n UpdateNavigationInput,\n StorageAdapter,\n SupabaseStorageAdapterConfig,\n} from './storage';\n\nexport {\n SupabaseStorageAdapter,\n createStorageAdapter,\n StorageError,\n handleCreatePage,\n handleUpdatePage,\n handleDeletePage,\n handleCreateNavigation,\n handleUpdateNavigation,\n handleDeleteNavigation,\n StorageValidationError,\n} from './storage';\n\nexport { generateSlug, ensureUniqueSlug } from './utils';\n\nexport type {\n PageResponse,\n NavigationResponse,\n ListPagesOptions,\n} from './delivery';\n\nexport {\n handleListPages,\n handleGetPageBySlug,\n handleGetNavigation,\n} from './delivery';\n\nexport type {\n MediaFile,\n MediaAdapter,\n UploadMediaInput,\n MediaFilter,\n AllowedMimeType,\n SupabaseMediaAdapterConfig,\n} from './media';\n\nexport {\n ALLOWED_MIME_TYPES,\n SupabaseMediaAdapter,\n createMediaAdapter,\n MediaError,\n handleUploadMedia,\n handleGetMedia,\n handleListMedia,\n handleDeleteMedia,\n MediaValidationError,\n resolveMediaReferences,\n} from './media';\n\nexport type {\n SupabaseAdapterFactoryStorageConfig,\n SupabaseAdapterFactoryConfig,\n SupabaseAdapters,\n} from './supabase';\n\nexport { createSupabaseAdapters } from './supabase';\n\nexport type {\n PageExportResponse,\n AllPagesExportResponse,\n NavigationExportResponse,\n AllNavigationsExportResponse,\n SiteExportResponse,\n MediaExportEntry,\n} from './export';\n\nexport {\n handleExportPage,\n handleExportAllPages,\n handleExportNavigations,\n handleExportSite,\n} from './export';\n\nexport type {\n AuthUser,\n AuthSession,\n SignInWithOAuthInput,\n SignInWithPasswordInput,\n OAuthResponse,\n VerifySessionInput,\n AuthAdapter,\n SupabaseAuthAdapterConfig,\n AuthMiddlewareConfig,\n AuthenticatedRequest,\n} from './auth';\n\nexport {\n SupabaseAuthAdapter,\n createAuthAdapter,\n AuthError,\n handleSignInWithOAuth,\n handleSignInWithPassword,\n handleSignOut,\n handleVerifySession,\n handleRefreshSession,\n handleGetCurrentUser,\n AuthValidationError,\n createAuthMiddleware,\n} from './auth';\n","import type { SupabaseClient } from '@supabase/supabase-js';\nimport type {\n CreateNavigationInput,\n CreatePageInput,\n Navigation,\n NavigationItem,\n Page,\n PageFilter,\n PageSection,\n StorageAdapter,\n UpdateNavigationInput,\n UpdatePageInput,\n} from './types';\n\n/**\n * Database row types (snake_case)\n */\ninterface PageRow {\n id: string;\n slug: string;\n page_type: string;\n title: string;\n sections: PageSection[];\n created_at: string;\n updated_at: string;\n}\n\ninterface NavigationRow {\n id: string;\n name: string;\n items: NavigationItem[];\n created_at: string;\n updated_at: string;\n}\n\n/**\n * Maps a database page row to the Page type\n */\nfunction mapPageRowToPage(row: PageRow): Page {\n return {\n id: row.id,\n slug: row.slug,\n pageType: row.page_type,\n title: row.title,\n sections: row.sections,\n createdAt: new Date(row.created_at),\n updatedAt: new Date(row.updated_at),\n };\n}\n\n/**\n * Maps a database navigation row to the Navigation type\n */\nfunction mapNavigationRowToNavigation(row: NavigationRow): Navigation {\n return {\n id: row.id,\n name: row.name,\n items: row.items,\n updatedAt: new Date(row.updated_at),\n };\n}\n\n/**\n * Storage adapter error with additional context\n */\nexport class StorageError extends Error {\n constructor(\n message: string,\n public readonly code?: string,\n public readonly details?: string\n ) {\n super(message);\n this.name = 'StorageError';\n }\n}\n\n/**\n * Configuration for creating a Supabase storage adapter\n */\nexport interface SupabaseStorageAdapterConfig {\n client: SupabaseClient;\n}\n\n/**\n * Supabase implementation of the StorageAdapter interface\n */\nexport class SupabaseStorageAdapter implements StorageAdapter {\n private client: SupabaseClient;\n\n constructor(config: SupabaseStorageAdapterConfig) {\n this.client = config.client;\n }\n\n async getPage(slug: string): Promise<Page | null> {\n const { data, error } = await this.client.from('pages').select('*').eq('slug', slug).single();\n\n if (error) {\n if (error.code === 'PGRST116') {\n return null; // Not found\n }\n throw new StorageError(error.message, error.code, error.details);\n }\n\n return mapPageRowToPage(data as PageRow);\n }\n\n async getPageById(id: string): Promise<Page | null> {\n const { data, error } = await this.client.from('pages').select('*').eq('id', id).single();\n\n if (error) {\n if (error.code === 'PGRST116') {\n return null; // Not found\n }\n throw new StorageError(error.message, error.code, error.details);\n }\n\n return mapPageRowToPage(data as PageRow);\n }\n\n async createPage(input: CreatePageInput): Promise<Page> {\n const { data, error } = await this.client\n .from('pages')\n .insert({\n slug: input.slug,\n page_type: input.pageType,\n title: input.title,\n sections: input.sections ?? [],\n })\n .select()\n .single();\n\n if (error) {\n throw new StorageError(error.message, error.code, error.details);\n }\n\n return mapPageRowToPage(data as PageRow);\n }\n\n async updatePage(input: UpdatePageInput): Promise<Page> {\n const updateData: Record<string, unknown> = {};\n\n if (input.slug !== undefined) {\n updateData.slug = input.slug;\n }\n if (input.pageType !== undefined) {\n updateData.page_type = input.pageType;\n }\n if (input.title !== undefined) {\n updateData.title = input.title;\n }\n if (input.sections !== undefined) {\n updateData.sections = input.sections;\n }\n\n const { data, error } = await this.client\n .from('pages')\n .update(updateData)\n .eq('id', input.id)\n .select()\n .single();\n\n if (error) {\n if (error.code === 'PGRST116') {\n throw new StorageError(`Page not found: ${input.id}`, 'NOT_FOUND');\n }\n throw new StorageError(error.message, error.code, error.details);\n }\n\n return mapPageRowToPage(data as PageRow);\n }\n\n async deletePage(id: string): Promise<void> {\n const { error } = await this.client.from('pages').delete().eq('id', id);\n\n if (error) {\n throw new StorageError(error.message, error.code, error.details);\n }\n }\n\n async listPages(filter?: PageFilter): Promise<Page[]> {\n let query = this.client.from('pages').select('*');\n\n if (filter?.pageType) {\n query = query.eq('page_type', filter.pageType);\n }\n\n query = query.order('created_at', { ascending: false });\n\n if (filter?.limit) {\n query = query.limit(filter.limit);\n }\n\n if (filter?.offset) {\n query = query.range(filter.offset, filter.offset + (filter.limit ?? 100) - 1);\n }\n\n const { data, error } = await query;\n\n if (error) {\n throw new StorageError(error.message, error.code, error.details);\n }\n\n return (data as PageRow[]).map(mapPageRowToPage);\n }\n\n async getNavigation(name: string): Promise<Navigation | null> {\n const { data, error } = await this.client\n .from('navigation')\n .select('*')\n .eq('name', name)\n .single();\n\n if (error) {\n if (error.code === 'PGRST116') {\n return null; // Not found\n }\n throw new StorageError(error.message, error.code, error.details);\n }\n\n return mapNavigationRowToNavigation(data as NavigationRow);\n }\n\n async getNavigationById(id: string): Promise<Navigation | null> {\n const { data, error } = await this.client.from('navigation').select('*').eq('id', id).single();\n\n if (error) {\n if (error.code === 'PGRST116') {\n return null; // Not found\n }\n throw new StorageError(error.message, error.code, error.details);\n }\n\n return mapNavigationRowToNavigation(data as NavigationRow);\n }\n\n async createNavigation(input: CreateNavigationInput): Promise<Navigation> {\n const { data, error } = await this.client\n .from('navigation')\n .insert({\n name: input.name,\n items: input.items,\n })\n .select()\n .single();\n\n if (error) {\n if (error.code === '23505') {\n throw new StorageError(\n `Navigation with name \"${input.name}\" already exists`,\n 'DUPLICATE_NAME'\n );\n }\n throw new StorageError(error.message, error.code, error.details);\n }\n\n return mapNavigationRowToNavigation(data as NavigationRow);\n }\n\n async updateNavigation(input: UpdateNavigationInput): Promise<Navigation> {\n const updateData: Record<string, unknown> = {};\n\n if (input.name !== undefined) {\n updateData.name = input.name;\n }\n if (input.items !== undefined) {\n updateData.items = input.items;\n }\n\n const { data, error } = await this.client\n .from('navigation')\n .update(updateData)\n .eq('id', input.id)\n .select()\n .single();\n\n if (error) {\n if (error.code === 'PGRST116') {\n throw new StorageError(`Navigation not found: ${input.id}`, 'NOT_FOUND');\n }\n throw new StorageError(error.message, error.code, error.details);\n }\n\n return mapNavigationRowToNavigation(data as NavigationRow);\n }\n\n async deleteNavigation(id: string): Promise<void> {\n const { error } = await this.client.from('navigation').delete().eq('id', id);\n\n if (error) {\n throw new StorageError(error.message, error.code, error.details);\n }\n }\n\n async listNavigations(): Promise<Navigation[]> {\n const { data, error } = await this.client\n .from('navigation')\n .select('*')\n .order('name', { ascending: true });\n\n if (error) {\n throw new StorageError(error.message, error.code, error.details);\n }\n\n return (data as NavigationRow[]).map(mapNavigationRowToNavigation);\n }\n}\n\n/**\n * Creates a storage adapter using Supabase\n */\nexport function createStorageAdapter(config: SupabaseStorageAdapterConfig): StorageAdapter {\n return new SupabaseStorageAdapter(config);\n}\n","import sanitizeHtml from 'sanitize-html';\nimport type { PageSection } from '../storage/types';\n\n/**\n * Allowed HTML tags for sanitization\n */\nconst ALLOWED_TAGS = [\n 'p',\n 'h1',\n 'h2',\n 'h3',\n 'h4',\n 'h5',\n 'h6',\n 'ul',\n 'ol',\n 'li',\n 'a',\n 'strong',\n 'em',\n 'br',\n 'blockquote',\n 'code',\n 'pre',\n 'img',\n];\n\n/**\n * Allowed HTML attributes per tag\n */\nconst ALLOWED_ATTRIBUTES: Record<string, string[]> = {\n a: ['href'],\n img: ['src', 'alt'],\n};\n\n/**\n * Sanitize-html configuration\n */\nconst SANITIZE_OPTIONS: sanitizeHtml.IOptions = {\n allowedTags: ALLOWED_TAGS,\n allowedAttributes: ALLOWED_ATTRIBUTES,\n disallowedTagsMode: 'discard',\n};\n\n/**\n * Sanitize a single string value using the configured allowlist\n */\nexport function sanitizeString(value: string): string {\n return sanitizeHtml(value, SANITIZE_OPTIONS);\n}\n\n/**\n * Recursively sanitize all string values in an unknown data structure\n */\nexport function sanitizeValue(value: unknown): unknown {\n if (typeof value === 'string') {\n return sanitizeString(value);\n }\n\n if (Array.isArray(value)) {\n return value.map(sanitizeValue);\n }\n\n if (value !== null && typeof value === 'object') {\n const result: Record<string, unknown> = {};\n for (const [key, val] of Object.entries(value as Record<string, unknown>)) {\n result[key] = sanitizeValue(val);\n }\n return result;\n }\n\n // number, boolean, null, undefined — pass through\n return value;\n}\n\n/**\n * Sanitize all string values in page sections' data\n * Returns a new array with sanitized sections (does not mutate input)\n */\nexport function sanitizeSectionData(sections: PageSection[]): PageSection[] {\n return sections.map((section) => ({\n ...section,\n data: sanitizeValue(section.data) as Record<string, unknown>,\n }));\n}\n","/**\n * Character replacements for German umlauts and special characters\n */\nconst UMLAUT_MAP: Record<string, string> = {\n ä: 'ae',\n ö: 'oe',\n ü: 'ue',\n Ä: 'Ae',\n Ö: 'Oe',\n Ü: 'Ue',\n ß: 'ss',\n};\n\n/**\n * Generates a URL-safe slug from a title string.\n *\n * @param title - The title to convert to a slug\n * @returns A URL-safe slug\n *\n * @example\n * generateSlug('Hello World') // 'hello-world'\n * generateSlug('Über uns') // 'ueber-uns'\n * generateSlug(' Multiple Spaces ') // 'multiple-spaces'\n */\nexport function generateSlug(title: string): string {\n let slug = title.toLowerCase().trim();\n\n // Replace German umlauts\n for (const [umlaut, replacement] of Object.entries(UMLAUT_MAP)) {\n slug = slug.replace(new RegExp(umlaut, 'g'), replacement.toLowerCase());\n }\n\n // Replace spaces and underscores with hyphens\n slug = slug.replace(/[\\s_]+/g, '-');\n\n // Remove all non-alphanumeric characters except hyphens\n slug = slug.replace(/[^a-z0-9-]/g, '');\n\n // Remove multiple consecutive hyphens\n slug = slug.replace(/-+/g, '-');\n\n // Remove leading and trailing hyphens\n slug = slug.replace(/^-+|-+$/g, '');\n\n return slug;\n}\n\n/**\n * Ensures a slug is unique by appending a numeric suffix if needed.\n *\n * @param slug - The base slug to make unique\n * @param existingSlugs - Array of existing slugs to check against\n * @returns A unique slug\n *\n * @example\n * ensureUniqueSlug('hello', ['hello', 'world']) // 'hello-1'\n * ensureUniqueSlug('hello', ['hello', 'hello-1']) // 'hello-2'\n * ensureUniqueSlug('new', ['hello', 'world']) // 'new'\n */\nexport function ensureUniqueSlug(slug: string, existingSlugs: string[]): string {\n if (!existingSlugs.includes(slug)) {\n return slug;\n }\n\n let counter = 1;\n let uniqueSlug = `${slug}-${counter}`;\n\n while (existingSlugs.includes(uniqueSlug)) {\n counter++;\n uniqueSlug = `${slug}-${counter}`;\n }\n\n return uniqueSlug;\n}\n","import { sanitizeSectionData } from '../utils/sanitize';\nimport { ensureUniqueSlug, generateSlug } from '../utils/slug';\nimport type {\n CreateNavigationInput,\n CreatePageInput,\n Navigation,\n Page,\n StorageAdapter,\n UpdateNavigationInput,\n UpdatePageInput,\n} from './types';\n\n/**\n * Error thrown when storage validation fails\n */\nexport class StorageValidationError extends Error {\n constructor(\n message: string,\n public readonly code: string\n ) {\n super(message);\n this.name = 'StorageValidationError';\n }\n}\n\n/**\n * Handler for creating a new page\n * Generates a slug from the title if not provided and ensures uniqueness\n */\nexport async function handleCreatePage(\n adapter: StorageAdapter,\n input: CreatePageInput\n): Promise<Page> {\n if (!input.title.trim()) {\n throw new StorageValidationError('Page title must not be empty', 'EMPTY_TITLE');\n }\n\n const slug = input.slug?.trim() || generateSlug(input.title);\n\n if (!slug) {\n throw new StorageValidationError(\n 'Could not generate a valid slug from the provided title',\n 'INVALID_SLUG'\n );\n }\n\n // Ensure slug uniqueness\n const existingPages = await adapter.listPages();\n const existingSlugs = existingPages.map((p) => p.slug);\n const uniqueSlug = ensureUniqueSlug(slug, existingSlugs);\n\n const sanitizedSections = input.sections ? sanitizeSectionData(input.sections) : undefined;\n\n return adapter.createPage({\n ...input,\n slug: uniqueSlug,\n sections: sanitizedSections,\n });\n}\n\n/**\n * Handler for updating an existing page\n */\nexport async function handleUpdatePage(\n adapter: StorageAdapter,\n input: UpdatePageInput\n): Promise<Page> {\n if (!input.id.trim()) {\n throw new StorageValidationError('Page ID must not be empty', 'EMPTY_ID');\n }\n\n if (input.title !== undefined && !input.title.trim()) {\n throw new StorageValidationError('Page title must not be empty', 'EMPTY_TITLE');\n }\n\n // If slug is being updated, ensure uniqueness\n if (input.slug !== undefined) {\n const slug = input.slug.trim();\n if (!slug) {\n throw new StorageValidationError('Page slug must not be empty', 'EMPTY_SLUG');\n }\n\n const existingPages = await adapter.listPages();\n const existingSlugs = existingPages.filter((p) => p.id !== input.id).map((p) => p.slug);\n\n if (existingSlugs.includes(slug)) {\n throw new StorageValidationError(`Slug \"${slug}\" is already in use`, 'DUPLICATE_SLUG');\n }\n }\n\n const sanitizedInput = input.sections\n ? { ...input, sections: sanitizeSectionData(input.sections) }\n : input;\n\n return adapter.updatePage(sanitizedInput);\n}\n\n/**\n * Handler for deleting a page by ID\n */\nexport async function handleDeletePage(adapter: StorageAdapter, id: string): Promise<void> {\n if (!id.trim()) {\n throw new StorageValidationError('Page ID must not be empty', 'EMPTY_ID');\n }\n\n return adapter.deletePage(id);\n}\n\n/**\n * Handler for creating a new navigation\n * Validates that the name is non-empty and unique\n */\nexport async function handleCreateNavigation(\n adapter: StorageAdapter,\n input: CreateNavigationInput\n): Promise<Navigation> {\n if (!input.name.trim()) {\n throw new StorageValidationError('Navigation name must not be empty', 'EMPTY_NAME');\n }\n\n // Ensure name uniqueness\n const existingNavigations = await adapter.listNavigations();\n const existingNames = existingNavigations.map((n) => n.name);\n\n if (existingNames.includes(input.name.trim())) {\n throw new StorageValidationError(\n `Navigation name \"${input.name.trim()}\" is already in use`,\n 'DUPLICATE_NAME'\n );\n }\n\n return adapter.createNavigation(input);\n}\n\n/**\n * Handler for updating an existing navigation\n */\nexport async function handleUpdateNavigation(\n adapter: StorageAdapter,\n input: UpdateNavigationInput\n): Promise<Navigation> {\n if (!input.id.trim()) {\n throw new StorageValidationError('Navigation ID must not be empty', 'EMPTY_ID');\n }\n\n // If name is being updated, ensure uniqueness\n if (input.name !== undefined) {\n const name = input.name.trim();\n if (!name) {\n throw new StorageValidationError('Navigation name must not be empty', 'EMPTY_NAME');\n }\n\n const existingNavigations = await adapter.listNavigations();\n const existingNames = existingNavigations.filter((n) => n.id !== input.id).map((n) => n.name);\n\n if (existingNames.includes(name)) {\n throw new StorageValidationError(\n `Navigation name \"${name}\" is already in use`,\n 'DUPLICATE_NAME'\n );\n }\n }\n\n return adapter.updateNavigation(input);\n}\n\n/**\n * Handler for deleting a navigation by ID\n */\nexport async function handleDeleteNavigation(adapter: StorageAdapter, id: string): Promise<void> {\n if (!id.trim()) {\n throw new StorageValidationError('Navigation ID must not be empty', 'EMPTY_ID');\n }\n\n return adapter.deleteNavigation(id);\n}\n","import type { PageSection } from '../storage/types';\nimport type { MediaAdapter } from './types';\n\nconst UUID_PATTERN = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;\n\n/**\n * Convention: fields ending with these suffixes are treated as media references\n */\nconst MEDIA_FIELD_SUFFIXES = ['_image', '_media', '_photo', '_thumbnail', '_avatar', '_icon'];\n\n/**\n * Checks if a field name is a media reference field by convention\n */\nfunction isMediaField(fieldName: string): boolean {\n const lower = fieldName.toLowerCase();\n if (\n lower === 'image' ||\n lower === 'media' ||\n lower === 'photo' ||\n lower === 'thumbnail' ||\n lower === 'avatar' ||\n lower === 'icon'\n ) {\n return true;\n }\n return MEDIA_FIELD_SUFFIXES.some((suffix) => lower.endsWith(suffix));\n}\n\n/**\n * Checks if a value looks like a media ID (UUID format)\n */\nfunction isMediaId(value: unknown): value is string {\n return typeof value === 'string' && UUID_PATTERN.test(value);\n}\n\n/**\n * Resolves media references in a single data object.\n * Walks all fields and replaces media IDs with public URLs.\n * Returns a new object with resolved references.\n */\nasync function resolveDataObject(\n data: Record<string, unknown>,\n adapter: MediaAdapter\n): Promise<Record<string, unknown>> {\n const resolved: Record<string, unknown> = {};\n\n for (const [key, value] of Object.entries(data)) {\n if (isMediaField(key) && isMediaId(value)) {\n const media = await adapter.getMedia(value);\n resolved[key] = media ? media.url : null;\n } else if (value !== null && typeof value === 'object' && !Array.isArray(value)) {\n resolved[key] = await resolveDataObject(value as Record<string, unknown>, adapter);\n } else {\n resolved[key] = value;\n }\n }\n\n return resolved;\n}\n\n/**\n * Resolves media references in page sections.\n * Walks section data fields and replaces media IDs (UUIDs) with public URLs.\n *\n * Convention: Fields named \"image\", \"media\", \"photo\", \"thumbnail\", \"avatar\", \"icon\"\n * or ending with \"_image\", \"_media\", etc. are treated as media references.\n *\n * Missing media is resolved to null.\n */\nexport async function resolveMediaReferences(\n sections: PageSection[],\n adapter: MediaAdapter\n): Promise<PageSection[]> {\n const resolved: PageSection[] = [];\n\n for (const section of sections) {\n const resolvedData = await resolveDataObject(section.data, adapter);\n resolved.push({\n id: section.id,\n type: section.type,\n data: resolvedData,\n });\n }\n\n return resolved;\n}\n","import { resolveMediaReferences } from '../media/resolve';\nimport type { MediaAdapter } from '../media/types';\nimport type { Navigation, Page, StorageAdapter } from '../storage/types';\nimport type { ListPagesOptions, NavigationResponse, PageResponse } from './types';\n\n/**\n * Maps internal Page type to PageResponse for delivery API with resolved media references\n */\nasync function toPageResponse(page: Page, mediaAdapter: MediaAdapter): Promise<PageResponse> {\n const resolvedSections = await resolveMediaReferences(page.sections, mediaAdapter);\n\n return {\n id: page.id,\n slug: page.slug,\n pageType: page.pageType,\n title: page.title,\n sections: resolvedSections,\n meta: {\n createdAt: page.createdAt.toISOString(),\n updatedAt: page.updatedAt.toISOString(),\n },\n };\n}\n\n/**\n * Maps internal Navigation type to NavigationResponse for delivery API\n */\nfunction toNavigationResponse(navigation: Navigation): NavigationResponse {\n return {\n id: navigation.id,\n name: navigation.name,\n items: navigation.items,\n meta: {\n updatedAt: navigation.updatedAt.toISOString(),\n },\n };\n}\n\n/**\n * Handler for GET /api/cms/pages\n * Returns all pages with resolved media references, optionally filtered by pageType\n */\nexport async function handleListPages(\n adapter: StorageAdapter,\n mediaAdapter: MediaAdapter,\n options?: ListPagesOptions\n): Promise<PageResponse[]> {\n const pages = await adapter.listPages({\n pageType: options?.pageType,\n limit: options?.limit,\n offset: options?.offset,\n });\n\n const results: PageResponse[] = [];\n for (const page of pages) {\n results.push(await toPageResponse(page, mediaAdapter));\n }\n\n return results;\n}\n\n/**\n * Handler for GET /api/cms/pages/:slug\n * Returns a single page by slug with resolved media references, or null if not found\n */\nexport async function handleGetPageBySlug(\n adapter: StorageAdapter,\n mediaAdapter: MediaAdapter,\n slug: string\n): Promise<PageResponse | null> {\n const page = await adapter.getPage(slug);\n\n if (!page) {\n return null;\n }\n\n return toPageResponse(page, mediaAdapter);\n}\n\n/**\n * Handler for GET /api/cms/navigation/:name\n * Returns a navigation by name, or null if not found\n */\nexport async function handleGetNavigation(\n adapter: StorageAdapter,\n name: string\n): Promise<NavigationResponse | null> {\n const navigation = await adapter.getNavigation(name);\n\n if (!navigation) {\n return null;\n }\n\n return toNavigationResponse(navigation);\n}\n","/**\n * Allowed MIME types for media uploads\n */\nexport const ALLOWED_MIME_TYPES = [\n 'image/jpeg',\n 'image/png',\n 'image/gif',\n 'image/webp',\n 'image/svg+xml',\n] as const;\n\nexport type AllowedMimeType = (typeof ALLOWED_MIME_TYPES)[number];\n\n/**\n * Represents a media file stored in the CMS\n */\nexport interface MediaFile {\n id: string;\n filename: string;\n url: string;\n mimeType: string;\n size: number;\n createdAt: Date;\n}\n\n/**\n * Input for uploading a media file\n * data accepts ArrayBuffer for platform-agnostic binary data\n */\nexport interface UploadMediaInput {\n filename: string;\n mimeType: string;\n size: number;\n data: ArrayBuffer | Uint8Array;\n}\n\n/**\n * Filter options for listing media files\n */\nexport interface MediaFilter {\n limit?: number;\n offset?: number;\n mimeType?: string;\n}\n\n/**\n * Media adapter interface for file storage operations.\n * This interface is Supabase-agnostic to allow future portability.\n */\nexport interface MediaAdapter {\n /**\n * Upload a media file\n */\n upload(input: UploadMediaInput): Promise<MediaFile>;\n\n /**\n * Get a media file by ID\n */\n getMedia(id: string): Promise<MediaFile | null>;\n\n /**\n * List media files with optional filtering and pagination\n */\n listMedia(filter?: MediaFilter): Promise<MediaFile[]>;\n\n /**\n * Delete a media file by ID\n */\n deleteMedia(id: string): Promise<void>;\n}\n","import type { SupabaseClient } from '@supabase/supabase-js';\nimport type { MediaAdapter, MediaFile, MediaFilter, UploadMediaInput } from './types';\n\n/**\n * Database row type for media (snake_case)\n */\ninterface MediaRow {\n id: string;\n filename: string;\n storage_path: string;\n mime_type: string;\n size: number;\n created_at: string;\n updated_at: string;\n}\n\n/**\n * Media adapter error with additional context\n */\nexport class MediaError extends Error {\n constructor(\n message: string,\n public readonly code?: string,\n public readonly details?: string\n ) {\n super(message);\n this.name = 'MediaError';\n }\n}\n\n/**\n * Configuration for creating a Supabase media adapter\n */\nexport interface SupabaseMediaAdapterConfig {\n client: SupabaseClient;\n bucketName?: string;\n}\n\n/**\n * Supabase implementation of the MediaAdapter interface\n */\nexport class SupabaseMediaAdapter implements MediaAdapter {\n private client: SupabaseClient;\n private bucketName: string;\n\n constructor(config: SupabaseMediaAdapterConfig) {\n this.client = config.client;\n this.bucketName = config.bucketName ?? 'media';\n }\n\n /**\n * Generates a unique storage path for a file\n */\n private generateStoragePath(filename: string): string {\n const timestamp = Date.now();\n const randomSuffix = Math.random().toString(36).substring(2, 8);\n const sanitizedFilename = filename.replace(/[^a-zA-Z0-9.-]/g, '_');\n return `${timestamp}-${randomSuffix}-${sanitizedFilename}`;\n }\n\n /**\n * Constructs the public URL for a stored file\n */\n private getPublicUrl(storagePath: string): string {\n const { data } = this.client.storage.from(this.bucketName).getPublicUrl(storagePath);\n return data.publicUrl;\n }\n\n /**\n * Maps a database row to MediaFile\n */\n private mapRowToMediaFile(row: MediaRow): MediaFile {\n return {\n id: row.id,\n filename: row.filename,\n url: this.getPublicUrl(row.storage_path),\n mimeType: row.mime_type,\n size: row.size,\n createdAt: new Date(row.created_at),\n };\n }\n\n async upload(input: UploadMediaInput): Promise<MediaFile> {\n const storagePath = this.generateStoragePath(input.filename);\n\n // Upload file to Supabase Storage\n const { error: uploadError } = await this.client.storage\n .from(this.bucketName)\n .upload(storagePath, input.data, {\n contentType: input.mimeType,\n upsert: false,\n });\n\n if (uploadError) {\n throw new MediaError(\n `Failed to upload file: ${uploadError.message}`,\n 'UPLOAD_FAILED',\n uploadError.message\n );\n }\n\n // Create database record\n const { data, error: dbError } = await this.client\n .from('media')\n .insert({\n filename: input.filename,\n storage_path: storagePath,\n mime_type: input.mimeType,\n size: input.size,\n })\n .select()\n .single();\n\n if (dbError) {\n // Cleanup: delete uploaded file if DB insert fails\n await this.client.storage.from(this.bucketName).remove([storagePath]);\n throw new MediaError(\n `Failed to create media record: ${dbError.message}`,\n dbError.code,\n dbError.details\n );\n }\n\n return this.mapRowToMediaFile(data as MediaRow);\n }\n\n async getMedia(id: string): Promise<MediaFile | null> {\n const { data, error } = await this.client.from('media').select('*').eq('id', id).single();\n\n if (error) {\n if (error.code === 'PGRST116') {\n return null; // Not found\n }\n throw new MediaError(error.message, error.code, error.details);\n }\n\n return this.mapRowToMediaFile(data as MediaRow);\n }\n\n async listMedia(filter?: MediaFilter): Promise<MediaFile[]> {\n let query = this.client.from('media').select('*');\n\n if (filter?.mimeType) {\n query = query.eq('mime_type', filter.mimeType);\n }\n\n query = query.order('created_at', { ascending: false });\n\n if (filter?.limit) {\n query = query.limit(filter.limit);\n }\n\n if (filter?.offset) {\n query = query.range(filter.offset, filter.offset + (filter.limit ?? 100) - 1);\n }\n\n const { data, error } = await query;\n\n if (error) {\n throw new MediaError(error.message, error.code, error.details);\n }\n\n return (data as MediaRow[]).map((row) => this.mapRowToMediaFile(row));\n }\n\n async deleteMedia(id: string): Promise<void> {\n // First get the media record to find the storage path\n const { data: mediaRecord, error: fetchError } = await this.client\n .from('media')\n .select('storage_path')\n .eq('id', id)\n .single();\n\n if (fetchError) {\n if (fetchError.code === 'PGRST116') {\n throw new MediaError(`Media not found: ${id}`, 'NOT_FOUND');\n }\n throw new MediaError(fetchError.message, fetchError.code, fetchError.details);\n }\n\n const storagePath = (mediaRecord as { storage_path: string }).storage_path;\n\n // Delete from storage\n const { error: storageError } = await this.client.storage\n .from(this.bucketName)\n .remove([storagePath]);\n\n if (storageError) {\n throw new MediaError(\n `Failed to delete file from storage: ${storageError.message}`,\n 'STORAGE_DELETE_FAILED',\n storageError.message\n );\n }\n\n // Delete database record\n const { error: dbError } = await this.client.from('media').delete().eq('id', id);\n\n if (dbError) {\n throw new MediaError(dbError.message, dbError.code, dbError.details);\n }\n }\n}\n\n/**\n * Creates a media adapter using Supabase\n */\nexport function createMediaAdapter(config: SupabaseMediaAdapterConfig): MediaAdapter {\n return new SupabaseMediaAdapter(config);\n}\n","import type { MediaAdapter, MediaFile, MediaFilter, UploadMediaInput } from './types';\nimport { ALLOWED_MIME_TYPES } from './types';\n\n/**\n * Error thrown when media validation fails\n */\nexport class MediaValidationError extends Error {\n constructor(\n message: string,\n public readonly code: string\n ) {\n super(message);\n this.name = 'MediaValidationError';\n }\n}\n\n/**\n * Validates that the MIME type is allowed\n */\nfunction validateMimeType(mimeType: string): void {\n const allowed = ALLOWED_MIME_TYPES as readonly string[];\n if (!allowed.includes(mimeType)) {\n throw new MediaValidationError(\n `Invalid file type: ${mimeType}. Allowed types: ${ALLOWED_MIME_TYPES.join(', ')}`,\n 'INVALID_MIME_TYPE'\n );\n }\n}\n\n/**\n * Handler for uploading a media file\n * Validates the file type and delegates to the adapter\n */\nexport async function handleUploadMedia(\n adapter: MediaAdapter,\n input: UploadMediaInput\n): Promise<MediaFile> {\n validateMimeType(input.mimeType);\n return adapter.upload(input);\n}\n\n/**\n * Handler for retrieving a media file by ID\n */\nexport async function handleGetMedia(adapter: MediaAdapter, id: string): Promise<MediaFile | null> {\n return adapter.getMedia(id);\n}\n\n/**\n * Handler for listing media files with optional filtering\n */\nexport async function handleListMedia(\n adapter: MediaAdapter,\n filter?: MediaFilter\n): Promise<MediaFile[]> {\n return adapter.listMedia(filter);\n}\n\n/**\n * Handler for deleting a media file\n */\nexport async function handleDeleteMedia(adapter: MediaAdapter, id: string): Promise<void> {\n return adapter.deleteMedia(id);\n}\n","import { createClient } from '@supabase/supabase-js';\nimport { createAuthAdapter } from '../auth';\nimport type { AuthAdapter } from '../auth';\nimport { createMediaAdapter } from '../media';\nimport type { MediaAdapter } from '../media';\nimport { createStorageAdapter } from '../storage';\nimport type { StorageAdapter } from '../storage';\n\nexport interface SupabaseAdapterFactoryStorageConfig {\n bucket?: string;\n}\n\nexport interface SupabaseAdapterFactoryConfig {\n url?: string;\n key?: string;\n storage?: SupabaseAdapterFactoryStorageConfig;\n}\n\nexport interface SupabaseAdapters {\n storageAdapter: StorageAdapter;\n mediaAdapter: MediaAdapter;\n authAdapter: AuthAdapter;\n}\n\ninterface ProcessLike {\n env: Record<string, string | undefined>;\n}\n\nfunction readEnv(name: string): string | undefined {\n const processLike = (globalThis as typeof globalThis & { process?: ProcessLike }).process;\n return processLike?.env[name];\n}\n\nfunction resolveRequiredConfig(\n explicitValue: string | undefined,\n envName: 'SUPABASE_URL' | 'SUPABASE_SECRET_KEY'\n): string {\n const explicit = explicitValue?.trim();\n if (explicit) {\n return explicit;\n }\n\n const fromEnv = readEnv(envName)?.trim();\n if (fromEnv) {\n return fromEnv;\n }\n\n throw new Error(\n `Missing Supabase configuration: provide ${envName} in factory config or environment variable`\n );\n}\n\nfunction resolveBucketName(config: SupabaseAdapterFactoryConfig): string {\n const explicitBucket = config.storage?.bucket?.trim();\n if (explicitBucket) {\n return explicitBucket;\n }\n\n const envBucket = readEnv('SUPABASE_STORAGE_BUCKET')?.trim();\n if (envBucket) {\n return envBucket;\n }\n\n return 'media';\n}\n\nexport function createSupabaseAdapters(\n config: SupabaseAdapterFactoryConfig = {}\n): SupabaseAdapters {\n const url = resolveRequiredConfig(config.url, 'SUPABASE_URL');\n const key = resolveRequiredConfig(config.key, 'SUPABASE_SECRET_KEY');\n const bucketName = resolveBucketName(config);\n\n const client = createClient(url, key);\n\n return {\n storageAdapter: createStorageAdapter({ client }),\n mediaAdapter: createMediaAdapter({ client, bucketName }),\n authAdapter: createAuthAdapter({ client }),\n };\n}\n","import type { SupabaseClient } from '@supabase/supabase-js';\nimport type {\n AuthAdapter,\n AuthSession,\n AuthUser,\n OAuthResponse,\n SignInWithOAuthInput,\n SignInWithPasswordInput,\n VerifySessionInput,\n} from './types';\n\nexport class AuthError extends Error {\n constructor(\n message: string,\n public readonly code?: string,\n public readonly details?: string\n ) {\n super(message);\n this.name = 'AuthError';\n }\n}\n\nexport interface SupabaseAuthAdapterConfig {\n client: SupabaseClient;\n}\n\nexport class SupabaseAuthAdapter implements AuthAdapter {\n private client: SupabaseClient;\n\n constructor(config: SupabaseAuthAdapterConfig) {\n this.client = config.client;\n }\n\n private mapSupabaseUser(user: {\n id: string;\n email?: string;\n user_metadata?: Record<string, unknown>;\n }): AuthUser {\n if (!user.email) {\n throw new AuthError('User email is required', 'INVALID_USER');\n }\n\n return {\n id: user.id,\n email: user.email,\n metadata: user.user_metadata,\n };\n }\n\n async signInWithOAuth(input: SignInWithOAuthInput): Promise<OAuthResponse> {\n const { data, error } = await this.client.auth.signInWithOAuth({\n provider: input.provider,\n options: {\n redirectTo: input.redirectTo,\n },\n });\n\n if (error) {\n throw new AuthError(error.message, error.status?.toString(), error.message);\n }\n\n if (!data.url) {\n throw new AuthError('OAuth URL not provided', 'OAUTH_URL_MISSING');\n }\n\n return {\n url: data.url,\n provider: input.provider,\n };\n }\n\n async signInWithPassword(input: SignInWithPasswordInput): Promise<AuthSession> {\n const { data, error } = await this.client.auth.signInWithPassword({\n email: input.email,\n password: input.password,\n });\n\n if (error) {\n throw new AuthError(error.message, error.status?.toString(), error.message);\n }\n\n if (!data.session || !data.user) {\n throw new AuthError('Session or user not returned', 'AUTH_FAILED');\n }\n\n return {\n accessToken: data.session.access_token,\n refreshToken: data.session.refresh_token,\n expiresAt: data.session.expires_at ? new Date(data.session.expires_at * 1000) : undefined,\n user: this.mapSupabaseUser(data.user),\n };\n }\n\n async signOut(_accessToken: string): Promise<void> {\n const { error } = await this.client.auth.signOut();\n\n if (error) {\n throw new AuthError(error.message, error.status?.toString(), error.message);\n }\n }\n\n async verifySession(input: VerifySessionInput): Promise<AuthUser | null> {\n const { data, error } = await this.client.auth.getUser(input.accessToken);\n\n if (error) {\n return null;\n }\n\n if (!data.user) {\n return null;\n }\n\n return this.mapSupabaseUser(data.user);\n }\n\n async refreshSession(refreshToken: string): Promise<AuthSession> {\n const { data, error } = await this.client.auth.refreshSession({\n refresh_token: refreshToken,\n });\n\n if (error) {\n throw new AuthError(error.message, error.status?.toString(), error.message);\n }\n\n if (!data.session || !data.user) {\n throw new AuthError('Session refresh failed', 'REFRESH_FAILED');\n }\n\n return {\n accessToken: data.session.access_token,\n refreshToken: data.session.refresh_token,\n expiresAt: data.session.expires_at ? new Date(data.session.expires_at * 1000) : undefined,\n user: this.mapSupabaseUser(data.user),\n };\n }\n\n async getCurrentUser(accessToken: string): Promise<AuthUser | null> {\n const { data, error } = await this.client.auth.getUser(accessToken);\n\n if (error) {\n return null;\n }\n\n if (!data.user) {\n return null;\n }\n\n return this.mapSupabaseUser(data.user);\n }\n}\n\nexport function createAuthAdapter(config: SupabaseAuthAdapterConfig): AuthAdapter {\n return new SupabaseAuthAdapter(config);\n}\n","import type {\n AuthAdapter,\n SignInWithOAuthInput,\n SignInWithPasswordInput,\n VerifySessionInput,\n} from './types';\n\nexport class AuthValidationError extends Error {\n constructor(message: string) {\n super(message);\n this.name = 'AuthValidationError';\n }\n}\n\nexport async function handleSignInWithOAuth(adapter: AuthAdapter, input: SignInWithOAuthInput) {\n if (!input.provider) {\n throw new AuthValidationError('Provider is required');\n }\n\n const validProviders = ['google', 'github', 'gitlab', 'azure', 'bitbucket'];\n if (!validProviders.includes(input.provider)) {\n throw new AuthValidationError(`Invalid provider. Must be one of: ${validProviders.join(', ')}`);\n }\n\n return await adapter.signInWithOAuth(input);\n}\n\n/**\n * Validate password strength\n * Requirements:\n * - Minimum 8 characters\n * - At least 3 of: uppercase, lowercase, numbers, special characters\n * - Not a common password\n */\nfunction validatePassword(password: string): void {\n if (password.length < 8) {\n throw new AuthValidationError('Password must be at least 8 characters');\n }\n\n const hasUpperCase = /[A-Z]/.test(password);\n const hasLowerCase = /[a-z]/.test(password);\n const hasNumbers = /\\d/.test(password);\n const hasSpecialChar = /[!@#$%^&*(),.?\":{}|<>_\\-+=\\[\\]\\\\/'`~]/.test(password);\n\n const complexityCount = [hasUpperCase, hasLowerCase, hasNumbers, hasSpecialChar].filter(\n Boolean\n ).length;\n\n if (complexityCount < 3) {\n throw new AuthValidationError(\n 'Password must contain at least 3 of: uppercase letters, lowercase letters, numbers, special characters'\n );\n }\n\n // Check against common passwords\n const commonPasswords = [\n 'password',\n 'password1',\n 'password123',\n '12345678',\n '123456789',\n 'qwerty',\n 'abc123',\n 'monkey',\n '1234567890',\n 'letmein',\n 'trustno1',\n 'dragon',\n 'baseball',\n 'iloveyou',\n 'master',\n 'sunshine',\n 'ashley',\n 'bailey',\n 'shadow',\n '123123',\n ];\n\n if (commonPasswords.includes(password.toLowerCase())) {\n throw new AuthValidationError('Password is too common. Please choose a stronger password.');\n }\n}\n\nexport async function handleSignInWithPassword(\n adapter: AuthAdapter,\n input: SignInWithPasswordInput\n) {\n if (!input.email || !input.password) {\n throw new AuthValidationError('Email and password are required');\n }\n\n if (!input.email.includes('@')) {\n throw new AuthValidationError('Invalid email format');\n }\n\n // Strengthen password policy (Fix #5)\n validatePassword(input.password);\n\n return await adapter.signInWithPassword(input);\n}\n\nexport async function handleSignOut(adapter: AuthAdapter, accessToken: string) {\n if (!accessToken) {\n throw new AuthValidationError('Access token is required');\n }\n\n return await adapter.signOut(accessToken);\n}\n\nexport async function handleVerifySession(adapter: AuthAdapter, input: VerifySessionInput) {\n if (!input.accessToken) {\n throw new AuthValidationError('Access token is required');\n }\n\n // Enforce token expiration (Fix #7)\n if (input.expiresAt) {\n const now = new Date();\n const expiresAt = input.expiresAt instanceof Date ? input.expiresAt : new Date(input.expiresAt);\n \n if (now > expiresAt) {\n throw new AuthValidationError('Token has expired');\n }\n }\n\n const user = await adapter.verifySession(input);\n\n if (!user) {\n throw new AuthValidationError('Invalid or expired token');\n }\n\n return user;\n}\n\nexport async function handleRefreshSession(adapter: AuthAdapter, refreshToken: string) {\n if (!refreshToken) {\n throw new AuthValidationError('Refresh token is required');\n }\n\n return await adapter.refreshSession(refreshToken);\n}\n\nexport async function handleGetCurrentUser(adapter: AuthAdapter, accessToken: string) {\n if (!accessToken) {\n throw new AuthValidationError('Access token is required');\n }\n\n return await adapter.getCurrentUser(accessToken);\n}\n","import type { AuthAdapter, AuthUser } from './types';\n\nexport interface AuthenticatedRequest {\n user: AuthUser;\n accessToken: string;\n}\n\nexport interface AuthMiddlewareConfig {\n adapter: AuthAdapter;\n extractToken?: (headers: Record<string, string | undefined>) => string | null;\n}\n\nexport function createAuthMiddleware(config: AuthMiddlewareConfig) {\n const extractToken =\n config.extractToken ||\n ((headers: Record<string, string | undefined>) => {\n const authHeader = headers.authorization || headers.Authorization;\n if (!authHeader) return null;\n\n const parts = authHeader.split(' ');\n if (parts.length !== 2 || parts[0] !== 'Bearer') return null;\n\n return parts[1];\n });\n\n return async function authenticate(\n headers: Record<string, string | undefined>\n ): Promise<AuthenticatedRequest> {\n const token = extractToken(headers);\n\n if (!token) {\n throw new Error('No authentication token provided');\n }\n\n const user = await config.adapter.verifySession({ accessToken: token });\n\n if (!user) {\n throw new Error('Invalid or expired token');\n }\n\n return {\n user,\n accessToken: token,\n };\n };\n}\n","import { resolveMediaReferences } from '../media/resolve';\nimport type { MediaAdapter } from '../media/types';\nimport type { Navigation, Page, StorageAdapter } from '../storage/types';\nimport type {\n AllNavigationsExportResponse,\n AllPagesExportResponse,\n MediaExportEntry,\n NavigationExportResponse,\n PageExportResponse,\n SiteExportResponse,\n} from './types';\n\n/**\n * Generates a Content-Disposition header value for file download\n */\nfunction contentDisposition(filename: string): string {\n return `attachment; filename=\"${filename}\"`;\n}\n\n/**\n * Converts a Page to a PageExportResponse with resolved media references\n */\nasync function toPageExport(page: Page, mediaAdapter: MediaAdapter): Promise<PageExportResponse> {\n const resolvedSections = await resolveMediaReferences(page.sections, mediaAdapter);\n\n return {\n id: page.id,\n slug: page.slug,\n pageType: page.pageType,\n title: page.title,\n sections: resolvedSections,\n createdAt: page.createdAt.toISOString(),\n updatedAt: page.updatedAt.toISOString(),\n };\n}\n\n/**\n * Handler for GET /api/cms/export/pages/:slug\n * Returns complete page JSON with resolved media URLs\n */\nexport async function handleExportPage(\n storageAdapter: StorageAdapter,\n mediaAdapter: MediaAdapter,\n slug: string\n): Promise<{ data: PageExportResponse; contentDisposition: string } | null> {\n const page = await storageAdapter.getPage(slug);\n\n if (!page) {\n return null;\n }\n\n const data = await toPageExport(page, mediaAdapter);\n\n return {\n data,\n contentDisposition: contentDisposition(`${page.slug}.json`),\n };\n}\n\n/**\n * Handler for GET /api/cms/export/pages\n * Returns all pages as JSON array with resolved media URLs\n */\nexport async function handleExportAllPages(\n storageAdapter: StorageAdapter,\n mediaAdapter: MediaAdapter\n): Promise<{ data: AllPagesExportResponse; contentDisposition: string }> {\n const pages = await storageAdapter.listPages();\n\n const exportedPages: PageExportResponse[] = [];\n for (const page of pages) {\n exportedPages.push(await toPageExport(page, mediaAdapter));\n }\n\n const data: AllPagesExportResponse = {\n pages: exportedPages,\n exportedAt: new Date().toISOString(),\n };\n\n return {\n data,\n contentDisposition: contentDisposition('pages-export.json'),\n };\n}\n\n/**\n * Converts a Navigation to a NavigationExportResponse\n */\nfunction toNavigationExport(nav: Navigation): NavigationExportResponse {\n return {\n id: nav.id,\n name: nav.name,\n items: nav.items,\n updatedAt: nav.updatedAt.toISOString(),\n };\n}\n\n/**\n * Handler for GET /api/cms/export/navigation\n * Returns all navigation structures as JSON\n */\nexport async function handleExportNavigations(\n storageAdapter: StorageAdapter\n): Promise<{ data: AllNavigationsExportResponse; contentDisposition: string }> {\n const navigations = await storageAdapter.listNavigations();\n\n const data: AllNavigationsExportResponse = {\n navigations: navigations.map(toNavigationExport),\n exportedAt: new Date().toISOString(),\n };\n\n return {\n data,\n contentDisposition: contentDisposition('navigation-export.json'),\n };\n}\n\n/**\n * Handler for GET /api/cms/export\n * Returns complete site content (pages, navigation, media metadata) as JSON\n */\nexport async function handleExportSite(\n storageAdapter: StorageAdapter,\n mediaAdapter: MediaAdapter\n): Promise<{ data: SiteExportResponse; contentDisposition: string }> {\n // Export pages with resolved media\n const pages = await storageAdapter.listPages();\n const exportedPages: PageExportResponse[] = [];\n for (const page of pages) {\n exportedPages.push(await toPageExport(page, mediaAdapter));\n }\n\n // Export navigations\n const navigations = await storageAdapter.listNavigations();\n const exportedNavigations = navigations.map(toNavigationExport);\n\n // Export media metadata\n const mediaFiles = await mediaAdapter.listMedia();\n const exportedMedia: MediaExportEntry[] = mediaFiles.map((file) => ({\n id: file.id,\n filename: file.filename,\n url: file.url,\n mimeType: file.mimeType,\n size: file.size,\n createdAt: file.createdAt.toISOString(),\n }));\n\n const data: SiteExportResponse = {\n pages: exportedPages,\n navigations: exportedNavigations,\n media: exportedMedia,\n exportedAt: new Date().toISOString(),\n };\n\n return {\n data,\n contentDisposition: contentDisposition('site-export.json'),\n };\n}\n"],"mappings":"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","names":["sanitizeHtml"]}
@@ -0,0 +1,365 @@
1
+ import { S as StorageAdapter, P as Page, C as CreatePageInput, U as UpdatePageInput, a as PageFilter, N as Navigation, b as CreateNavigationInput, c as UpdateNavigationInput, d as NavigationItem, e as PageSection, M as MediaAdapter, f as UploadMediaInput, g as MediaFile, h as MediaFilter, A as AuthAdapter, i as SignInWithOAuthInput, O as OAuthResponse, j as SignInWithPasswordInput, k as AuthSession, V as VerifySessionInput, l as AuthUser } from './types-Zi0Iyow1.cjs';
2
+ export { m as ALLOWED_MIME_TYPES, n as AllowedMimeType } from './types-Zi0Iyow1.cjs';
3
+ import { SupabaseClient } from '@supabase/supabase-js';
4
+ export { SupabaseAdapterFactoryConfig, SupabaseAdapterFactoryStorageConfig, SupabaseAdapters, createSupabaseAdapters } from './supabase/index.cjs';
5
+
6
+ /**
7
+ * Storage adapter error with additional context
8
+ */
9
+ declare class StorageError extends Error {
10
+ readonly code?: string | undefined;
11
+ readonly details?: string | undefined;
12
+ constructor(message: string, code?: string | undefined, details?: string | undefined);
13
+ }
14
+ /**
15
+ * Configuration for creating a Supabase storage adapter
16
+ */
17
+ interface SupabaseStorageAdapterConfig {
18
+ client: SupabaseClient;
19
+ }
20
+ /**
21
+ * Supabase implementation of the StorageAdapter interface
22
+ */
23
+ declare class SupabaseStorageAdapter implements StorageAdapter {
24
+ private client;
25
+ constructor(config: SupabaseStorageAdapterConfig);
26
+ getPage(slug: string): Promise<Page | null>;
27
+ getPageById(id: string): Promise<Page | null>;
28
+ createPage(input: CreatePageInput): Promise<Page>;
29
+ updatePage(input: UpdatePageInput): Promise<Page>;
30
+ deletePage(id: string): Promise<void>;
31
+ listPages(filter?: PageFilter): Promise<Page[]>;
32
+ getNavigation(name: string): Promise<Navigation | null>;
33
+ getNavigationById(id: string): Promise<Navigation | null>;
34
+ createNavigation(input: CreateNavigationInput): Promise<Navigation>;
35
+ updateNavigation(input: UpdateNavigationInput): Promise<Navigation>;
36
+ deleteNavigation(id: string): Promise<void>;
37
+ listNavigations(): Promise<Navigation[]>;
38
+ }
39
+ /**
40
+ * Creates a storage adapter using Supabase
41
+ */
42
+ declare function createStorageAdapter(config: SupabaseStorageAdapterConfig): StorageAdapter;
43
+
44
+ /**
45
+ * Error thrown when storage validation fails
46
+ */
47
+ declare class StorageValidationError extends Error {
48
+ readonly code: string;
49
+ constructor(message: string, code: string);
50
+ }
51
+ /**
52
+ * Handler for creating a new page
53
+ * Generates a slug from the title if not provided and ensures uniqueness
54
+ */
55
+ declare function handleCreatePage(adapter: StorageAdapter, input: CreatePageInput): Promise<Page>;
56
+ /**
57
+ * Handler for updating an existing page
58
+ */
59
+ declare function handleUpdatePage(adapter: StorageAdapter, input: UpdatePageInput): Promise<Page>;
60
+ /**
61
+ * Handler for deleting a page by ID
62
+ */
63
+ declare function handleDeletePage(adapter: StorageAdapter, id: string): Promise<void>;
64
+ /**
65
+ * Handler for creating a new navigation
66
+ * Validates that the name is non-empty and unique
67
+ */
68
+ declare function handleCreateNavigation(adapter: StorageAdapter, input: CreateNavigationInput): Promise<Navigation>;
69
+ /**
70
+ * Handler for updating an existing navigation
71
+ */
72
+ declare function handleUpdateNavigation(adapter: StorageAdapter, input: UpdateNavigationInput): Promise<Navigation>;
73
+ /**
74
+ * Handler for deleting a navigation by ID
75
+ */
76
+ declare function handleDeleteNavigation(adapter: StorageAdapter, id: string): Promise<void>;
77
+
78
+ /**
79
+ * Generates a URL-safe slug from a title string.
80
+ *
81
+ * @param title - The title to convert to a slug
82
+ * @returns A URL-safe slug
83
+ *
84
+ * @example
85
+ * generateSlug('Hello World') // 'hello-world'
86
+ * generateSlug('Über uns') // 'ueber-uns'
87
+ * generateSlug(' Multiple Spaces ') // 'multiple-spaces'
88
+ */
89
+ declare function generateSlug(title: string): string;
90
+ /**
91
+ * Ensures a slug is unique by appending a numeric suffix if needed.
92
+ *
93
+ * @param slug - The base slug to make unique
94
+ * @param existingSlugs - Array of existing slugs to check against
95
+ * @returns A unique slug
96
+ *
97
+ * @example
98
+ * ensureUniqueSlug('hello', ['hello', 'world']) // 'hello-1'
99
+ * ensureUniqueSlug('hello', ['hello', 'hello-1']) // 'hello-2'
100
+ * ensureUniqueSlug('new', ['hello', 'world']) // 'new'
101
+ */
102
+ declare function ensureUniqueSlug(slug: string, existingSlugs: string[]): string;
103
+
104
+ /**
105
+ * Page response for delivery API
106
+ * Dates are serialized as ISO strings for JSON transport
107
+ */
108
+ interface PageResponse {
109
+ id: string;
110
+ slug: string;
111
+ pageType: string;
112
+ title: string;
113
+ sections: PageSection[];
114
+ meta: {
115
+ createdAt: string;
116
+ updatedAt: string;
117
+ };
118
+ }
119
+ /**
120
+ * Navigation response for delivery API
121
+ */
122
+ interface NavigationResponse {
123
+ id: string;
124
+ name: string;
125
+ items: NavigationItem[];
126
+ meta: {
127
+ updatedAt: string;
128
+ };
129
+ }
130
+ /**
131
+ * Filter options for listing pages via delivery API
132
+ */
133
+ interface ListPagesOptions {
134
+ pageType?: string;
135
+ limit?: number;
136
+ offset?: number;
137
+ }
138
+
139
+ /**
140
+ * Handler for GET /api/cms/pages
141
+ * Returns all pages with resolved media references, optionally filtered by pageType
142
+ */
143
+ declare function handleListPages(adapter: StorageAdapter, mediaAdapter: MediaAdapter, options?: ListPagesOptions): Promise<PageResponse[]>;
144
+ /**
145
+ * Handler for GET /api/cms/pages/:slug
146
+ * Returns a single page by slug with resolved media references, or null if not found
147
+ */
148
+ declare function handleGetPageBySlug(adapter: StorageAdapter, mediaAdapter: MediaAdapter, slug: string): Promise<PageResponse | null>;
149
+ /**
150
+ * Handler for GET /api/cms/navigation/:name
151
+ * Returns a navigation by name, or null if not found
152
+ */
153
+ declare function handleGetNavigation(adapter: StorageAdapter, name: string): Promise<NavigationResponse | null>;
154
+
155
+ /**
156
+ * Media adapter error with additional context
157
+ */
158
+ declare class MediaError extends Error {
159
+ readonly code?: string | undefined;
160
+ readonly details?: string | undefined;
161
+ constructor(message: string, code?: string | undefined, details?: string | undefined);
162
+ }
163
+ /**
164
+ * Configuration for creating a Supabase media adapter
165
+ */
166
+ interface SupabaseMediaAdapterConfig {
167
+ client: SupabaseClient;
168
+ bucketName?: string;
169
+ }
170
+ /**
171
+ * Supabase implementation of the MediaAdapter interface
172
+ */
173
+ declare class SupabaseMediaAdapter implements MediaAdapter {
174
+ private client;
175
+ private bucketName;
176
+ constructor(config: SupabaseMediaAdapterConfig);
177
+ /**
178
+ * Generates a unique storage path for a file
179
+ */
180
+ private generateStoragePath;
181
+ /**
182
+ * Constructs the public URL for a stored file
183
+ */
184
+ private getPublicUrl;
185
+ /**
186
+ * Maps a database row to MediaFile
187
+ */
188
+ private mapRowToMediaFile;
189
+ upload(input: UploadMediaInput): Promise<MediaFile>;
190
+ getMedia(id: string): Promise<MediaFile | null>;
191
+ listMedia(filter?: MediaFilter): Promise<MediaFile[]>;
192
+ deleteMedia(id: string): Promise<void>;
193
+ }
194
+ /**
195
+ * Creates a media adapter using Supabase
196
+ */
197
+ declare function createMediaAdapter(config: SupabaseMediaAdapterConfig): MediaAdapter;
198
+
199
+ /**
200
+ * Error thrown when media validation fails
201
+ */
202
+ declare class MediaValidationError extends Error {
203
+ readonly code: string;
204
+ constructor(message: string, code: string);
205
+ }
206
+ /**
207
+ * Handler for uploading a media file
208
+ * Validates the file type and delegates to the adapter
209
+ */
210
+ declare function handleUploadMedia(adapter: MediaAdapter, input: UploadMediaInput): Promise<MediaFile>;
211
+ /**
212
+ * Handler for retrieving a media file by ID
213
+ */
214
+ declare function handleGetMedia(adapter: MediaAdapter, id: string): Promise<MediaFile | null>;
215
+ /**
216
+ * Handler for listing media files with optional filtering
217
+ */
218
+ declare function handleListMedia(adapter: MediaAdapter, filter?: MediaFilter): Promise<MediaFile[]>;
219
+ /**
220
+ * Handler for deleting a media file
221
+ */
222
+ declare function handleDeleteMedia(adapter: MediaAdapter, id: string): Promise<void>;
223
+
224
+ /**
225
+ * Resolves media references in page sections.
226
+ * Walks section data fields and replaces media IDs (UUIDs) with public URLs.
227
+ *
228
+ * Convention: Fields named "image", "media", "photo", "thumbnail", "avatar", "icon"
229
+ * or ending with "_image", "_media", etc. are treated as media references.
230
+ *
231
+ * Missing media is resolved to null.
232
+ */
233
+ declare function resolveMediaReferences(sections: PageSection[], adapter: MediaAdapter): Promise<PageSection[]>;
234
+
235
+ declare class AuthError extends Error {
236
+ readonly code?: string | undefined;
237
+ readonly details?: string | undefined;
238
+ constructor(message: string, code?: string | undefined, details?: string | undefined);
239
+ }
240
+ interface SupabaseAuthAdapterConfig {
241
+ client: SupabaseClient;
242
+ }
243
+ declare class SupabaseAuthAdapter implements AuthAdapter {
244
+ private client;
245
+ constructor(config: SupabaseAuthAdapterConfig);
246
+ private mapSupabaseUser;
247
+ signInWithOAuth(input: SignInWithOAuthInput): Promise<OAuthResponse>;
248
+ signInWithPassword(input: SignInWithPasswordInput): Promise<AuthSession>;
249
+ signOut(_accessToken: string): Promise<void>;
250
+ verifySession(input: VerifySessionInput): Promise<AuthUser | null>;
251
+ refreshSession(refreshToken: string): Promise<AuthSession>;
252
+ getCurrentUser(accessToken: string): Promise<AuthUser | null>;
253
+ }
254
+ declare function createAuthAdapter(config: SupabaseAuthAdapterConfig): AuthAdapter;
255
+
256
+ declare class AuthValidationError extends Error {
257
+ constructor(message: string);
258
+ }
259
+ declare function handleSignInWithOAuth(adapter: AuthAdapter, input: SignInWithOAuthInput): Promise<OAuthResponse>;
260
+ declare function handleSignInWithPassword(adapter: AuthAdapter, input: SignInWithPasswordInput): Promise<AuthSession>;
261
+ declare function handleSignOut(adapter: AuthAdapter, accessToken: string): Promise<void>;
262
+ declare function handleVerifySession(adapter: AuthAdapter, input: VerifySessionInput): Promise<AuthUser>;
263
+ declare function handleRefreshSession(adapter: AuthAdapter, refreshToken: string): Promise<AuthSession>;
264
+ declare function handleGetCurrentUser(adapter: AuthAdapter, accessToken: string): Promise<AuthUser | null>;
265
+
266
+ interface AuthenticatedRequest {
267
+ user: AuthUser;
268
+ accessToken: string;
269
+ }
270
+ interface AuthMiddlewareConfig {
271
+ adapter: AuthAdapter;
272
+ extractToken?: (headers: Record<string, string | undefined>) => string | null;
273
+ }
274
+ declare function createAuthMiddleware(config: AuthMiddlewareConfig): (headers: Record<string, string | undefined>) => Promise<AuthenticatedRequest>;
275
+
276
+ /**
277
+ * Export response for a single page with resolved media URLs
278
+ */
279
+ interface PageExportResponse {
280
+ id: string;
281
+ slug: string;
282
+ pageType: string;
283
+ title: string;
284
+ sections: PageSection[];
285
+ createdAt: string;
286
+ updatedAt: string;
287
+ }
288
+ /**
289
+ * Export response for all pages
290
+ */
291
+ interface AllPagesExportResponse {
292
+ pages: PageExportResponse[];
293
+ exportedAt: string;
294
+ }
295
+ /**
296
+ * Export response for navigation structures
297
+ */
298
+ interface NavigationExportResponse {
299
+ id: string;
300
+ name: string;
301
+ items: NavigationItem[];
302
+ updatedAt: string;
303
+ }
304
+ /**
305
+ * Export response for all navigations
306
+ */
307
+ interface AllNavigationsExportResponse {
308
+ navigations: NavigationExportResponse[];
309
+ exportedAt: string;
310
+ }
311
+ /**
312
+ * Full site export response
313
+ */
314
+ interface SiteExportResponse {
315
+ pages: PageExportResponse[];
316
+ navigations: NavigationExportResponse[];
317
+ media: MediaExportEntry[];
318
+ exportedAt: string;
319
+ }
320
+ /**
321
+ * Media entry in site export
322
+ */
323
+ interface MediaExportEntry {
324
+ id: string;
325
+ filename: string;
326
+ url: string;
327
+ mimeType: string;
328
+ size: number;
329
+ createdAt: string;
330
+ }
331
+
332
+ /**
333
+ * Handler for GET /api/cms/export/pages/:slug
334
+ * Returns complete page JSON with resolved media URLs
335
+ */
336
+ declare function handleExportPage(storageAdapter: StorageAdapter, mediaAdapter: MediaAdapter, slug: string): Promise<{
337
+ data: PageExportResponse;
338
+ contentDisposition: string;
339
+ } | null>;
340
+ /**
341
+ * Handler for GET /api/cms/export/pages
342
+ * Returns all pages as JSON array with resolved media URLs
343
+ */
344
+ declare function handleExportAllPages(storageAdapter: StorageAdapter, mediaAdapter: MediaAdapter): Promise<{
345
+ data: AllPagesExportResponse;
346
+ contentDisposition: string;
347
+ }>;
348
+ /**
349
+ * Handler for GET /api/cms/export/navigation
350
+ * Returns all navigation structures as JSON
351
+ */
352
+ declare function handleExportNavigations(storageAdapter: StorageAdapter): Promise<{
353
+ data: AllNavigationsExportResponse;
354
+ contentDisposition: string;
355
+ }>;
356
+ /**
357
+ * Handler for GET /api/cms/export
358
+ * Returns complete site content (pages, navigation, media metadata) as JSON
359
+ */
360
+ declare function handleExportSite(storageAdapter: StorageAdapter, mediaAdapter: MediaAdapter): Promise<{
361
+ data: SiteExportResponse;
362
+ contentDisposition: string;
363
+ }>;
364
+
365
+ export { type AllNavigationsExportResponse, type AllPagesExportResponse, AuthAdapter, AuthError, type AuthMiddlewareConfig, AuthSession, AuthUser, AuthValidationError, type AuthenticatedRequest, CreateNavigationInput, CreatePageInput, type ListPagesOptions, MediaAdapter, MediaError, type MediaExportEntry, MediaFile, MediaFilter, MediaValidationError, Navigation, type NavigationExportResponse, NavigationItem, type NavigationResponse, OAuthResponse, Page, type PageExportResponse, PageFilter, type PageResponse, PageSection, SignInWithOAuthInput, SignInWithPasswordInput, type SiteExportResponse, StorageAdapter, StorageError, StorageValidationError, SupabaseAuthAdapter, type SupabaseAuthAdapterConfig, SupabaseMediaAdapter, type SupabaseMediaAdapterConfig, SupabaseStorageAdapter, type SupabaseStorageAdapterConfig, UpdateNavigationInput, UpdatePageInput, UploadMediaInput, VerifySessionInput, createAuthAdapter, createAuthMiddleware, createMediaAdapter, createStorageAdapter, ensureUniqueSlug, generateSlug, handleCreateNavigation, handleCreatePage, handleDeleteMedia, handleDeleteNavigation, handleDeletePage, handleExportAllPages, handleExportNavigations, handleExportPage, handleExportSite, handleGetCurrentUser, handleGetMedia, handleGetNavigation, handleGetPageBySlug, handleListMedia, handleListPages, handleRefreshSession, handleSignInWithOAuth, handleSignInWithPassword, handleSignOut, handleUpdateNavigation, handleUpdatePage, handleUploadMedia, handleVerifySession, resolveMediaReferences };