feat: add allowed Linear API operations to MCP config

This commit is contained in:
Do Siki
2025-09-05 03:10:17 +02:00
parent 7e7d3fb1cc
commit b6365543ef
36 changed files with 14648 additions and 1 deletions
+197
View File
@@ -0,0 +1,197 @@
import winston from 'winston'
// Mock winston and winston-loki
jest.mock('winston', () => ({
format: {
combine: jest.fn(),
timestamp: jest.fn(),
errors: jest.fn(),
json: jest.fn(),
colorize: jest.fn(),
simple: jest.fn(),
printf: jest.fn()
},
transports: {
Console: jest.fn(),
File: jest.fn()
},
createLogger: jest.fn()
}))
jest.mock('winston-loki', () => jest.fn())
describe('Logger', () => {
beforeEach(() => {
jest.clearAllMocks()
// Reset process.env
process.env = {
...process.env,
NODE_ENV: 'test',
LOKI_HOST: undefined,
LOKI_USERNAME: undefined,
LOKI_PASSWORD: undefined
}
})
it('should create logger with correct configuration', () => {
// Mock the format functions
const mockFormat = {
combine: jest.fn().mockReturnValue('combined-format'),
timestamp: jest.fn().mockReturnValue('timestamp-format'),
errors: jest.fn().mockReturnValue('errors-format'),
json: jest.fn().mockReturnValue('json-format'),
colorize: jest.fn().mockReturnValue('colorize-format'),
simple: jest.fn().mockReturnValue('simple-format'),
printf: jest.fn().mockReturnValue('printf-format')
}
const mockTransports = {
Console: jest.fn().mockImplementation(() => ({ name: 'console' })),
File: jest.fn().mockImplementation(() => ({ name: 'file' }))
}
// Setup mocks
;(winston.format as any) = mockFormat
;(winston.transports as any) = mockTransports
;(winston.createLogger as jest.Mock) = jest.fn().mockReturnValue({
info: jest.fn(),
error: jest.fn(),
debug: jest.fn(),
warn: jest.fn(),
child: jest.fn().mockReturnValue({
info: jest.fn(),
error: jest.fn(),
debug: jest.fn()
}),
end: jest.fn()
})
// Import after mocks are set up
const { logger } = require('./logger')
expect(winston.createLogger).toHaveBeenCalledWith(
expect.objectContaining({
level: 'debug',
format: 'combined-format',
defaultMeta: expect.objectContaining({
service: 'mozdit-web',
environment: 'test'
}),
transports: expect.arrayContaining([
expect.objectContaining({ name: 'console' })
])
})
)
})
it('should include Loki transport when LOKI_HOST is provided', () => {
// Set LOKI_HOST
process.env.LOKI_HOST = 'http://localhost:3100'
process.env.LOKI_USERNAME = 'test'
process.env.LOKI_PASSWORD = 'testpass'
// Reset modules to pick up new env vars
jest.resetModules()
// Mock LokiTransport
const mockLokiTransport = jest.fn().mockImplementation(() => ({
name: 'loki'
}))
jest.doMock('winston-loki', () => mockLokiTransport)
// Import after mocks are set up
require('./logger')
expect(mockLokiTransport).toHaveBeenCalledWith(
expect.objectContaining({
host: 'http://localhost:3100',
labels: expect.objectContaining({
app: 'mozdit-web',
environment: 'test',
service: 'frontend'
}),
basicAuth: 'test:testpass'
})
)
})
it('should create component loggers with correct metadata', () => {
// Setup mocks
const mockLogger = {
child: jest.fn().mockReturnValue({
info: jest.fn(),
error: jest.fn()
})
}
;(winston.createLogger as jest.Mock) = jest.fn().mockReturnValue(mockLogger)
// Import after mocks are set up
const { createComponentLogger } = require('./logger')
const componentLogger = createComponentLogger('test-component')
expect(mockLogger.child).toHaveBeenCalledWith({ component: 'test-component' })
expect(componentLogger).toBeDefined()
})
it('should generate request IDs in correct format', () => {
// Setup mocks
jest.resetModules()
const { generateRequestId } = require('./logger')
const requestId = generateRequestId()
expect(requestId).toMatch(/^\d+-[a-z0-9]+$/)
})
it('should handle timing operations correctly', async () => {
// Setup mocks
const mockLogger = {
debug: jest.fn(),
error: jest.fn()
}
;(winston.createLogger as jest.Mock) = jest.fn().mockReturnValue(mockLogger)
jest.resetModules()
const { withTiming } = require('./logger')
const mockOperation = jest.fn().mockResolvedValue('success')
const result = await withTiming('test-operation', mockOperation, { test: 'metadata' })
expect(result).toBe('success')
expect(mockLogger.debug).toHaveBeenCalledTimes(2) // Start and complete
expect(mockLogger.debug).toHaveBeenCalledWith(
expect.stringContaining('Started test-operation'),
{ test: 'metadata' }
)
})
it('should handle timing operation failures', async () => {
// Setup mocks
const mockLogger = {
debug: jest.fn(),
error: jest.fn()
}
;(winston.createLogger as jest.Mock) = jest.fn().mockReturnValue(mockLogger)
jest.resetModules()
const { withTiming } = require('./logger')
const mockOperation = jest.fn().mockRejectedValue(new Error('test error'))
await expect(withTiming('test-operation', mockOperation)).rejects.toThrow('test error')
expect(mockLogger.debug).toHaveBeenCalledWith(
expect.stringContaining('Started test-operation'),
{}
)
expect(mockLogger.error).toHaveBeenCalledWith(
expect.stringContaining('Failed test-operation'),
expect.objectContaining({
duration: expect.any(Number),
error: expect.any(Error)
})
)
})
})
+120
View File
@@ -0,0 +1,120 @@
import winston from 'winston'
import LokiTransport from 'winston-loki'
const isDevelopment = process.env.NODE_ENV === 'development'
// Custom format for structured logging
const structuredFormat = winston.format.combine(
winston.format.timestamp({ format: 'ISO' }),
winston.format.errors({ stack: true }),
winston.format.json({
replacer: (_key, value) =>
typeof value === 'bigint' ? value.toString() : value,
})
)
// Console format for development
const consoleFormat = winston.format.combine(
winston.format.timestamp({ format: 'HH:mm:ss' }),
winston.format.errors({ stack: true }),
winston.format.colorize(),
winston.format.simple(),
winston.format.printf(({ timestamp, level, message, service, requestId, ...meta }) => {
const requestInfo = requestId ? `[${requestId}]` : ''
const serviceInfo = service ? `[${service}]` : '[mozdIT]'
const metaStr = Object.keys(meta).length ? `\n${JSON.stringify(meta, null, 2)}` : ''
return `${timestamp} ${serviceInfo} ${level} ${requestInfo} ${message}${metaStr}`
})
)
// Transports configuration
const transports: winston.transport[] = [
// Loki transport for centralized logging
...(process.env.LOKI_HOST
? [
new LokiTransport({
host: process.env.LOKI_HOST,
labels: {
app: 'mozdit-web',
environment: process.env.NODE_ENV || 'development',
service: 'frontend'
},
basicAuth: process.env.LOKI_USERNAME && process.env.LOKI_PASSWORD
? `${process.env.LOKI_USERNAME}:${process.env.LOKI_PASSWORD}`
: undefined,
json: true,
format: winston.format.json(),
onConnectionError: (err: Error) => console.error('Loki connection error:', err)
})
]
: []),
// Console for development logging
new winston.transports.Console({
level: isDevelopment ? 'debug' : 'info',
format: isDevelopment ? consoleFormat : structuredFormat,
handleExceptions: true,
handleRejections: true
})
]
// Root logger configuration
export const logger = winston.createLogger({
level: isDevelopment ? 'debug' : 'info',
format: structuredFormat,
defaultMeta: {
service: 'mozdit-web',
version: process.env.npm_package_version || '1.0.0',
environment: process.env.NODE_ENV || 'development'
},
transports,
exceptionHandlers: transports,
rejectionHandlers: transports
})
// Specialized loggers for different components
export const createComponentLogger = (component: string) => {
return logger.child({ component })
}
export const requestLogger = logger.child({ component: 'request' })
export const apiLogger = logger.child({ component: 'api' })
export const dbLogger = logger.child({ component: 'database' })
export const authLogger = logger.child({ component: 'auth' })
export const errorLogger = logger.child({ component: 'error' })
// Request ID generator for correlation
export const generateRequestId = (): string =>
`${Date.now()}-${Math.random().toString(36).substr(2, 9)}`
// Helper function for timing operations
export const withTiming = async <T>(
operation: string,
fn: () => Promise<T>,
metadata: any = {}
): Promise<T> => {
const startTime = Date.now()
logger.debug(`Started ${operation}`, metadata)
try {
const result = await fn()
const duration = Date.now() - startTime
logger.debug(`Completed ${operation} in ${duration}ms`, { ...metadata, duration })
return result
} catch (error) {
const duration = Date.now() - startTime
logger.error(`Failed ${operation} in ${duration}ms`, { ...metadata, duration, error })
throw error
}
}
// Graceful shutdown
const gracefulShutdown = () => {
logger.info('Initiating graceful shutdown...')
logger.end()
}
process.on('SIGTERM', gracefulShutdown)
process.on('SIGINT', gracefulShutdown)
export default logger
+154
View File
@@ -0,0 +1,154 @@
import {
getCollection,
getDb,
checkMongoConnection
} from './mongodb'
import { MongoClient } from 'mongodb'
const mockMongoClient = MongoClient as jest.MockedClass<typeof MongoClient>
// Mock the MongoDB client and database
const mockDb = {
collection: jest.fn().mockReturnValue({
findOne: jest.fn(),
insertOne: jest.fn(),
updateOne: jest.fn(),
deleteOne: jest.fn()
})
}
const mockClient = {
connect: jest.fn().mockResolvedValue(undefined),
close: jest.fn().mockResolvedValue(undefined),
db: jest.fn().mockReturnValue(mockDb)
}
// Mock the mongodb module
jest.mock('mongodb', () => ({
MongoClient: jest.fn().mockImplementation(() => mockClient)
}))
// Restore environment before tests
const originalEnv = process.env
describe('MongoDB Connection', () => {
beforeEach(() => {
process.env = {
...originalEnv,
MONGODB_URI: 'mongodb://localhost:27017/test',
MONGODB_DB: 'test'
}
jest.clearAllMocks()
})
afterEach(() => {
process.env = originalEnv
})
describe('MongoDB URI validation', () => {
it('should throw error when MONGODB_URI is not set', () => {
// This test is tricky because the module is cached
// We'll test this by clearing the module cache and mocking process.env
const originalMongodbUri = process.env.MONGODB_URI
delete process.env.MONGODB_URI
// Clear module cache to force re-import
jest.resetModules()
expect(() => {
require('./mongodb')
}).toThrow('Please add MONGODB_URI to your environment variables')
// Restore environment
process.env.MONGODB_URI = originalMongodbUri
})
})
describe('Database connection', () => {
it('should create MongoClient with correct URI and options', async () => {
const { MongoClient } = require('mongodb')
// Reset modules to use our mock
jest.resetModules()
const { clientPromise } = require('./mongodb')
await clientPromise
expect(MongoClient).toHaveBeenCalledWith(
process.env.MONGODB_URI,
expect.objectContaining({
maxPoolSize: 10,
serverSelectionTimeoutMS: 5000,
socketTimeoutMS: 45000
})
)
})
it('should return database instance', async () => {
// Reset modules to use our mock
jest.resetModules()
const { getDb } = require('./mongodb')
const result = await getDb()
expect(result).toBeDefined()
expect(typeof result.collection).toBe('function')
})
it('should return collection from database', async () => {
// Reset modules to use our mock
jest.resetModules()
const { getCollection } = require('./mongodb')
const collection = await getCollection('test_collection')
expect(collection).toBeDefined()
expect(typeof collection.findOne).toBe('function')
})
it('should check MongoDB connection successfully', async () => {
// Reset modules to use our mock
jest.resetModules()
const { checkMongoConnection } = require('./mongodb')
const result = await checkMongoConnection()
expect(result).toBe(true)
})
it('should return false on MongoDB connection failure', async () => {
// Mock a connection failure
const { MongoClient } = require('mongodb')
// Create a failing client
const failingClient = {
connect: jest.fn().mockRejectedValue(new Error('Connection failed')),
db: jest.fn(),
close: jest.fn()
}
MongoClient.mockImplementation(() => failingClient)
// Reset the module to use the new mock
jest.resetModules()
const { checkMongoConnection } = require('./mongodb')
const result = await checkMongoConnection()
expect(result).toBe(false)
})
})
describe('Connection reuse and caching', () => {
it('should reuse the same MongoClient instance for multiple calls', async () => {
// Reset modules to use our mock
jest.resetModules()
const { clientPromise: clientPromise1 } = require('./mongodb')
const { clientPromise: clientPromise2 } = require('./mongodb')
await clientPromise1
await clientPromise2
// Should still be the same promise from cache
expect(clientPromise1).toBe(clientPromise2)
})
})
})
+55
View File
@@ -0,0 +1,55 @@
import { MongoClient, Db } from 'mongodb'
if (!process.env.MONGODB_URI) {
throw new Error('Please add MONGODB_URI to your environment variables')
}
const uri = process.env.MONGODB_URI
const options = {
maxPoolSize: 10,
serverSelectionTimeoutMS: 5000,
socketTimeoutMS: 45000,
}
let client: MongoClient
let clientPromise: Promise<MongoClient>
// In development mode, use a global variable so that the client is not recreated between hot reloads
if (process.env.NODE_ENV === 'development') {
// @ts-ignore
if (!global._mongoClientPromise) {
client = new MongoClient(uri, options)
// @ts-ignore
global._mongoClientPromise = client.connect()
}
// @ts-ignore
clientPromise = global._mongoClientPromise
} else {
// In production mode, it's best to not use a global variable
client = new MongoClient(uri, options)
clientPromise = client.connect()
}
export default clientPromise
export async function getDb(): Promise<Db> {
const client = await clientPromise
return client.db(process.env.MONGODB_DB || 'mozdit')
}
export async function getCollection(collectionName: string) {
const db = await getDb()
return db.collection(collectionName)
}
// Health check for MongoDB connection
export async function checkMongoConnection(): Promise<boolean> {
try {
const db = await getDb()
await db.admin().ping()
return true
} catch (error) {
console.error('MongoDB connection check failed:', error)
return false
}
}
+382
View File
@@ -0,0 +1,382 @@
import { getSiteConfig, saveSiteConfig, initializeDefaultConfig } from './site-config'
import { getCollection } from './mongodb'
import { siteConfig as staticConfig } from '@/config/site'
import { SiteConfig } from '@/types/site'
import logger from './logger'
// Mock dependencies
jest.mock('./mongodb')
jest.mock('./logger', () => ({
__esModule: true,
default: {
info: jest.fn(),
warn: jest.fn(),
error: jest.fn()
}
}))
jest.mock('@/config/site', () => ({
siteConfig: {
general: {
name: 'Test Site',
description: 'Test Description',
url: 'https://test.com',
ogImage: 'https://test.com/og.jpg',
locale: 'en'
},
navigation: {
main: [
{ label: 'Home', href: '/' },
{ label: 'About', href: '/about' }
],
footer: [
{ label: 'Privacy', href: '/privacy' },
{ label: 'Terms', href: '/terms' }
]
},
hero: {
title: 'Welcome',
subtitle: 'Test Subtitle',
description: 'Test description',
cta: {
primary: {
text: 'Get Started',
href: '/get-started'
}
}
},
services: {
title: 'Our Services',
subtitle: 'What we offer',
services: []
},
about: {
title: 'About Us',
description: ['Test about section'],
usps: []
},
footer: {
copyright: '© 2024 Test Site',
links: []
},
contact: {
email: 'test@test.com',
address: 'Test Address',
form: {
title: 'Contact Us',
description: 'Get in touch',
submitText: 'Send Message',
fields: {
name: { label: 'Name', placeholder: 'Your name', required: true },
email: { label: 'Email', placeholder: 'your@email.com', required: true },
message: { label: 'Message', placeholder: 'Your message', required: true },
consent: { label: 'I agree', required: true }
}
}
}
}
}))
const mockCollection = {
findOne: jest.fn(),
replaceOne: jest.fn()
}
describe('Site Config', () => {
beforeEach(() => {
jest.clearAllMocks()
;(getCollection as jest.Mock).mockResolvedValue(mockCollection)
})
describe('getSiteConfig', () => {
it('should return MongoDB config when available', async () => {
const mockConfig: SiteConfig = {
general: {
name: 'MongoDB Site',
description: 'MongoDB Description',
url: 'https://mongodb.com',
ogImage: 'https://mongodb.com/og.jpg',
locale: 'en'
},
navigation: {
main: [{ label: 'Home', href: '/' }],
footer: [{ label: 'Privacy', href: '/privacy' }]
},
hero: {
title: 'MongoDB Hero',
subtitle: 'MongoDB Subtitle',
description: 'MongoDB Description',
cta: {
primary: {
text: 'Get Started',
href: '/get-started'
}
}
},
services: {
title: 'Services',
subtitle: 'Our services',
services: []
},
about: {
title: 'About',
description: ['About us'],
usps: []
},
footer: {
copyright: '© 2024 MongoDB',
links: []
},
contact: {
email: 'mongodb@test.com',
address: 'MongoDB Address',
form: {
title: 'Contact',
description: 'Get in touch',
submitText: 'Send',
fields: {
name: { label: 'Name', placeholder: 'Name', required: true },
email: { label: 'Email', placeholder: 'Email', required: true },
message: { label: 'Message', placeholder: 'Message', required: true },
consent: { label: 'Consent', required: true }
}
}
}
}
mockCollection.findOne.mockResolvedValue({
_id: 'mock-id',
data: mockConfig
})
const config = await getSiteConfig()
expect(config).toEqual(mockConfig)
expect(mockCollection.findOne).toHaveBeenCalledWith({
type: 'site_config',
environment: 'development'
})
expect(logger.info).toHaveBeenCalledWith(
'Loaded site config from MongoDB',
{ configId: 'mock-id' }
)
})
it('should return static config when MongoDB config not found', async () => {
mockCollection.findOne.mockResolvedValue(null)
const config = await getSiteConfig()
expect(config).toEqual(staticConfig)
expect(logger.info).toHaveBeenCalledWith(
'MongoDB config not found, using static fallback'
)
})
it('should return static config when MongoDB is unavailable', async () => {
mockCollection.findOne.mockRejectedValue(new Error('Connection failed'))
const config = await getSiteConfig()
expect(config).toEqual(staticConfig)
expect(logger.warn).toHaveBeenCalledWith(
'MongoDB unavailable, using static config',
{ error: 'Connection failed' }
)
})
})
describe('saveSiteConfig', () => {
it('should save config to MongoDB successfully', async () => {
const newConfig: SiteConfig = {
general: {
name: 'New Site',
description: 'New Description',
url: 'https://new.com',
ogImage: 'https://new.com/og.jpg',
locale: 'en'
},
navigation: {
main: [{ label: 'Home', href: '/' }],
footer: [{ label: 'Privacy', href: '/privacy' }]
},
hero: {
title: 'New Hero',
subtitle: 'New Subtitle',
description: 'New Description',
cta: {
primary: {
text: 'Get Started',
href: '/get-started'
}
}
},
services: {
title: 'Services',
subtitle: 'Our services',
services: []
},
about: {
title: 'About',
description: ['About us'],
usps: []
},
footer: {
copyright: '© 2024 New',
links: []
},
contact: {
email: 'new@test.com',
address: 'New Address',
form: {
title: 'Contact',
description: 'Get in touch',
submitText: 'Send',
fields: {
name: { label: 'Name', placeholder: 'Name', required: true },
email: { label: 'Email', placeholder: 'Email', required: true },
message: { label: 'Message', placeholder: 'Message', required: true },
consent: { label: 'Consent', required: true }
}
}
}
}
mockCollection.replaceOne.mockResolvedValue({ acknowledged: true })
const result = await saveSiteConfig(newConfig)
expect(result).toBe(true)
expect(mockCollection.replaceOne).toHaveBeenCalledWith(
{ type: 'site_config', environment: 'development' },
{
type: 'site_config',
environment: 'development',
data: newConfig,
lastModified: expect.any(Date)
},
{ upsert: true }
)
expect(logger.info).toHaveBeenCalledWith('Saved site config to MongoDB')
})
it('should return false when save fails', async () => {
const newConfig: SiteConfig = {
general: {
name: 'New Site',
description: 'New Description',
url: 'https://new.com',
ogImage: 'https://new.com/og.jpg',
locale: 'en'
},
navigation: {
main: [{ label: 'Home', href: '/' }],
footer: [{ label: 'Privacy', href: '/privacy' }]
},
hero: {
title: 'New Hero',
subtitle: 'New Subtitle',
description: 'New Description',
cta: {
primary: {
text: 'Get Started',
href: '/get-started'
}
}
},
services: {
title: 'Services',
subtitle: 'Our services',
services: []
},
about: {
title: 'About',
description: ['About us'],
usps: []
},
footer: {
copyright: '© 2024 New',
links: []
},
contact: {
email: 'new@test.com',
address: 'New Address',
form: {
title: 'Contact',
description: 'Get in touch',
submitText: 'Send',
fields: {
name: { label: 'Name', placeholder: 'Name', required: true },
email: { label: 'Email', placeholder: 'Email', required: true },
message: { label: 'Message', placeholder: 'Message', required: true },
consent: { label: 'Consent', required: true }
}
}
}
}
mockCollection.replaceOne.mockRejectedValue(new Error('Save failed'))
const result = await saveSiteConfig(newConfig)
expect(result).toBe(false)
expect(logger.error).toHaveBeenCalledWith(
'Failed to save config to MongoDB',
{ error: 'Save failed' }
)
})
})
describe('initializeDefaultConfig', () => {
it('should initialize default config when none exists', async () => {
mockCollection.findOne.mockResolvedValue(null)
mockCollection.replaceOne.mockResolvedValue({ acknowledged: true })
await initializeDefaultConfig()
expect(mockCollection.findOne).toHaveBeenCalledWith({
type: 'site_config',
environment: 'development'
})
expect(mockCollection.replaceOne).toHaveBeenCalledWith(
{ type: 'site_config', environment: 'development' },
{
type: 'site_config',
environment: 'development',
data: staticConfig,
lastModified: expect.any(Date)
},
{ upsert: true }
)
expect(logger.info).toHaveBeenCalledWith(
'Initialized default site config in MongoDB'
)
})
it('should not initialize when config already exists', async () => {
mockCollection.findOne.mockResolvedValue({
_id: 'existing-id',
data: staticConfig
})
await initializeDefaultConfig()
expect(mockCollection.replaceOne).not.toHaveBeenCalled()
expect(logger.info).toHaveBeenCalledWith(
'Site config already exists in MongoDB'
)
})
it('should handle initialization errors gracefully', async () => {
mockCollection.findOne.mockRejectedValue(new Error('Connection failed'))
await initializeDefaultConfig()
expect(logger.error).toHaveBeenCalledWith(
'Failed to initialize site config',
{ error: 'Connection failed' }
)
})
})
})
+74
View File
@@ -0,0 +1,74 @@
// Hybrid approach: File-based fallback + MongoDB integration for future
import { siteConfig as staticConfig } from '@/config/site'
import { SiteConfig } from '@/types/site'
import { getCollection } from './mongodb'
import logger from './logger'
/**
* Get site configuration with hybrid approach
* 1. Try MongoDB first (production-ready)
* 2. Fall back to static file (development/development safe)
*/
export async function getSiteConfig(): Promise<SiteConfig> {
try {
const collection = await getCollection('site_config')
const doc = await collection.findOne({ type: 'site_config', environment: 'development' })
if (doc && doc.data) {
logger.info('Loaded site config from MongoDB', { configId: doc._id })
return doc.data as SiteConfig
} else {
logger.info('MongoDB config not found, using static fallback')
return staticConfig
}
} catch (error) {
logger.warn('MongoDB unavailable, using static config', { error: (error as Error).message })
return staticConfig
}
}
/**
* Save site configuration to MongoDB
* For future admin panel integration
*/
export async function saveSiteConfig(config: SiteConfig): Promise<boolean> {
try {
const collection = await getCollection('site_config')
await collection.replaceOne(
{ type: 'site_config', environment: 'development' },
{
type: 'site_config',
environment: 'development',
data: config,
lastModified: new Date()
},
{ upsert: true }
)
logger.info('Saved site config to MongoDB')
return true
} catch (error) {
logger.error('Failed to save config to MongoDB', { error: (error as Error).message })
return false
}
}
/**
* Initialize default config in MongoDB (one-time setup)
*/
export async function initializeDefaultConfig(): Promise<void> {
try {
const collection = await getCollection('site_config')
const existingConfig = await collection.findOne({ type: 'site_config', environment: 'development' })
if (!existingConfig) {
await saveSiteConfig(staticConfig)
logger.info('Initialized default site config in MongoDB')
} else {
logger.info('Site config already exists in MongoDB')
}
} catch (error) {
logger.error('Failed to initialize site config', { error: (error as Error).message })
}
}