UNPKG

@restnfeel/agentc-starter-kit

Version:

한국어 기업용 CMS 모듈 - Task Master AI와 함께 빠르게 웹사이트를 구현할 수 있는 재사용 가능한 컴포넌트 시스템

588 lines (533 loc) 17.5 kB
/** * Template Factory System * 서비스 티어별 템플릿 생성 및 관리 시스템 */ import { v4 as uuidv4 } from "uuid"; import { TemplateFactory, BaseTemplate, ServiceTier, ComponentType, TemplateValidationResult, TemplateValidationError, TemplateValidationWarning, GlobalTemplateSettings, CustomizationOptions, TemplateMetadata, BaseComponent, ComponentSettings, } from "../../types/template-system"; import { componentRegistry } from "./component-registry"; export class AgentCTemplateFactory implements TemplateFactory { /** * 새 템플릿 생성 */ create(tier: ServiceTier, config: Partial<BaseTemplate>): BaseTemplate { const templateId = config.id || uuidv4(); const slug = config.slug || this.generateSlug(config.name || `template-${tier.toLowerCase()}`); const template: BaseTemplate = { id: templateId, name: config.name || `${tier} Template`, slug, tier, description: config.description || this.getDefaultDescription(tier), components: this.createDefaultComponents(tier), globalSettings: this.createDefaultGlobalSettings(tier), customizationOptions: this.createDefaultCustomizationOptions(tier), metadata: this.createDefaultMetadata(tier, config), ...config, }; return template; } /** * 템플릿 복제 */ clone(template: BaseTemplate): BaseTemplate { const newId = uuidv4(); const newSlug = this.generateSlug(`${template.slug}-copy`); return { ...JSON.parse(JSON.stringify(template)), // Deep clone id: newId, slug: newSlug, name: `${template.name} (Copy)`, metadata: { ...template.metadata, lastModified: new Date(), version: "1.0.0", }, }; } /** * 템플릿을 다른 티어로 마이그레이션 */ migrate(template: BaseTemplate, newTier: ServiceTier): BaseTemplate { const migratedTemplate = this.clone(template); migratedTemplate.tier = newTier; migratedTemplate.name = `${template.name} (${newTier})`; // 컴포넌트 필터링 - 새 티어에서 지원하지 않는 컴포넌트 제거 migratedTemplate.components = this.filterComponentsForTier( template.components, newTier ); // 커스터마이제이션 옵션 업데이트 migratedTemplate.customizationOptions = this.updateCustomizationOptionsForTier( template.customizationOptions, newTier ); // 글로벌 설정 업데이트 migratedTemplate.globalSettings = this.updateGlobalSettingsForTier( template.globalSettings, newTier ); // 메타데이터 업데이트 migratedTemplate.metadata = { ...template.metadata, minimumTier: newTier, lastModified: new Date(), version: "1.0.0", }; return migratedTemplate; } /** * 템플릿 검증 */ validate(template: BaseTemplate): TemplateValidationResult { const errors: TemplateValidationError[] = []; const warnings: TemplateValidationWarning[] = []; // 기본 필드 검증 if (!template.id || !template.name || !template.slug) { errors.push({ code: "MISSING_REQUIRED_FIELDS", message: "필수 필드(id, name, slug)가 누락되었습니다.", severity: "error", }); } // 컴포넌트 검증 template.components.forEach((component, index) => { if (!componentRegistry.validate(component, template.tier)) { errors.push({ code: "INVALID_COMPONENT", message: `컴포넌트 ${component.type}가 ${template.tier} 티어에서 유효하지 않습니다.`, component: component.id, severity: "error", }); } // 중복 컴포넌트 타입 검사 const duplicates = template.components.filter( (c, i) => i !== index && c.type === component.type ); if (duplicates.length > 0) { warnings.push({ code: "DUPLICATE_COMPONENT_TYPE", message: `중복된 컴포넌트 타입 ${component.type}이 발견되었습니다.`, component: component.id, suggestion: "중복된 컴포넌트를 제거하거나 다른 타입으로 변경하세요.", }); } }); // 티어별 제한사항 검증 const tierRestrictions = componentRegistry.getTierRestrictions( template.tier ); template.components.forEach((component) => { if (!tierRestrictions.allowedComponents.includes(component.type)) { errors.push({ code: "TIER_RESTRICTION_VIOLATION", message: `컴포넌트 ${component.type}는 ${template.tier} 티어에서 허용되지 않습니다.`, component: component.id, severity: "error", }); } }); // 커스터마이제이션 옵션 검증 if ( template.tier === ServiceTier.STARTER && template.customizationOptions.advanced.allowCustomCSS ) { warnings.push({ code: "ADVANCED_FEATURE_IN_STARTER", message: "Starter 티어에서 고급 CSS 커스터마이제이션이 활성화되어 있습니다.", suggestion: "Starter 티어에서는 고급 기능을 비활성화하는 것을 권장합니다.", }); } return { isValid: errors.length === 0, errors, warnings, }; } /** * 티어별 기본 컴포넌트 생성 */ private createDefaultComponents(tier: ServiceTier): BaseComponent[] { const tierRestrictions = componentRegistry.getTierRestrictions(tier); const components: BaseComponent[] = []; // 필수 컴포넌트들을 순서대로 추가 const requiredComponents = [ ComponentType.HERO, ComponentType.ABOUT, ComponentType.CONTACT, ComponentType.FOOTER, ]; requiredComponents.forEach((type, index) => { if (tierRestrictions.allowedComponents.includes(type)) { components.push(this.createComponent(type, tier, index)); } }); // 티어별 추가 컴포넌트 if (tier === ServiceTier.STANDARD || tier === ServiceTier.PLUS) { const standardComponents = [ ComponentType.SERVICES, ComponentType.TESTIMONIALS, ]; standardComponents.forEach((type, index) => { components.push( this.createComponent(type, tier, requiredComponents.length + index) ); }); } if (tier === ServiceTier.PLUS) { const plusComponents = [ComponentType.TEAM, ComponentType.GALLERY]; plusComponents.forEach((type, index) => { const order = requiredComponents.length + 2 + index; components.push(this.createComponent(type, tier, order)); }); } return components; } /** * 개별 컴포넌트 생성 */ private createComponent( type: ComponentType, tier: ServiceTier, order: number ): BaseComponent { const settings: ComponentSettings = { enabled: true, visible: true, customizable: tier !== ServiceTier.STARTER, }; return { id: uuidv4(), type, title: this.getComponentTitle(type), description: this.getComponentDescription(type), isRequired: this.isComponentRequired(type), availableIn: this.getComponentAvailability(type), order, settings, }; } /** * 컴포넌트 제목 반환 */ private getComponentTitle(type: ComponentType): string { const titles: Record<ComponentType, string> = { [ComponentType.HERO]: "메인 히어로 섹션", [ComponentType.ABOUT]: "소개 섹션", [ComponentType.SERVICES]: "서비스 섹션", [ComponentType.TESTIMONIALS]: "고객 후기", [ComponentType.BLOG]: "블로그", [ComponentType.CONTACT]: "연락처", [ComponentType.FOOTER]: "푸터", [ComponentType.TEAM]: "팀 소개", [ComponentType.GALLERY]: "갤러리", [ComponentType.NEWSLETTER]: "뉴스레터", [ComponentType.CASE_STUDIES]: "사례 연구", }; return titles[type]; } /** * 컴포넌트 설명 반환 */ private getComponentDescription(type: ComponentType): string { const descriptions: Record<ComponentType, string> = { [ComponentType.HERO]: "사이트의 첫인상을 결정하는 메인 배너 영역", [ComponentType.ABOUT]: "회사나 개인에 대한 소개 내용", [ComponentType.SERVICES]: "제공하는 서비스나 상품 안내", [ComponentType.TESTIMONIALS]: "고객들의 후기와 평가", [ComponentType.BLOG]: "블로그 포스트 목록 및 내용", [ComponentType.CONTACT]: "연락처 정보 및 문의 양식", [ComponentType.FOOTER]: "사이트 하단 정보 영역", [ComponentType.TEAM]: "팀 멤버 소개", [ComponentType.GALLERY]: "이미지 갤러리", [ComponentType.NEWSLETTER]: "뉴스레터 구독 양식", [ComponentType.CASE_STUDIES]: "프로젝트 사례 연구", }; return descriptions[type]; } /** * 컴포넌트 필수 여부 반환 */ private isComponentRequired(type: ComponentType): boolean { const requiredComponents = [ComponentType.HERO, ComponentType.FOOTER]; return requiredComponents.includes(type); } /** * 컴포넌트 사용 가능 티어 반환 */ private getComponentAvailability(type: ComponentType): ServiceTier[] { const starterComponents = [ ComponentType.HERO, ComponentType.ABOUT, ComponentType.CONTACT, ComponentType.FOOTER, ]; const standardComponents = [ ...starterComponents, ComponentType.SERVICES, ComponentType.TESTIMONIALS, ComponentType.BLOG, ]; if (starterComponents.includes(type)) return [ServiceTier.STARTER, ServiceTier.STANDARD, ServiceTier.PLUS]; if (standardComponents.includes(type)) return [ServiceTier.STANDARD, ServiceTier.PLUS]; return [ServiceTier.PLUS]; } /** * 티어별 기본 글로벌 설정 생성 */ private createDefaultGlobalSettings( tier: ServiceTier ): GlobalTemplateSettings { const baseSettings: GlobalTemplateSettings = { theme: { primaryColor: "#3b82f6", secondaryColor: "#64748b", fontFamily: "Inter, sans-serif", borderRadius: 8, }, layout: { containerWidth: "contained", spacing: "normal", headerStyle: "simple", footerStyle: "minimal", }, seo: { metaTitle: undefined, metaDescription: undefined, structuredData: false, }, performance: { lazyLoading: true, cacheStrategy: "basic", optimizeImages: true, }, }; // 티어별 설정 향상 if (tier === ServiceTier.STANDARD) { baseSettings.layout.headerStyle = "standard"; baseSettings.layout.footerStyle = "standard"; baseSettings.seo.structuredData = true; baseSettings.theme.accentColor = "#10b981"; } if (tier === ServiceTier.PLUS) { baseSettings.layout.headerStyle = "advanced"; baseSettings.layout.footerStyle = "comprehensive"; baseSettings.performance.cacheStrategy = "aggressive"; baseSettings.theme.accentColor = "#8b5cf6"; } return baseSettings; } /** * 티어별 기본 커스터마이제이션 옵션 생성 */ private createDefaultCustomizationOptions( tier: ServiceTier ): CustomizationOptions { const baseOptions: CustomizationOptions = { colors: { allowCustomColors: false, predefinedPalettes: [ ["#3b82f6", "#64748b"], ["#10b981", "#059669"], ["#f59e0b", "#d97706"], ], }, typography: { allowFontChange: false, availableFonts: ["Inter", "Roboto"], allowFontSizeChange: false, }, layout: { allowLayoutChange: false, availableLayouts: ["standard"], allowComponentReordering: false, }, components: { allowComponentToggle: false, allowComponentSettings: false, restrictedComponents: [], }, advanced: { allowCustomCSS: false, allowCustomJS: false, allowThirdPartyIntegrations: false, }, }; // Standard 티어 향상 if (tier === ServiceTier.STANDARD) { baseOptions.colors.allowCustomColors = true; baseOptions.colors.maxCustomColors = 3; baseOptions.typography.allowFontChange = true; baseOptions.typography.availableFonts.push("Poppins", "Open Sans"); baseOptions.layout.allowLayoutChange = true; baseOptions.layout.availableLayouts.push("wide", "boxed"); baseOptions.components.allowComponentToggle = true; } // Plus 티어 모든 기능 허용 if (tier === ServiceTier.PLUS) { baseOptions.colors.allowCustomColors = true; baseOptions.colors.maxCustomColors = 10; baseOptions.typography.allowFontChange = true; baseOptions.typography.allowFontSizeChange = true; baseOptions.typography.availableFonts.push( "Montserrat", "Lato", "Playfair Display" ); baseOptions.layout.allowLayoutChange = true; baseOptions.layout.allowComponentReordering = true; baseOptions.layout.availableLayouts.push("wide", "boxed", "full-width"); baseOptions.components.allowComponentToggle = true; baseOptions.components.allowComponentSettings = true; baseOptions.advanced.allowCustomCSS = true; baseOptions.advanced.allowThirdPartyIntegrations = true; } return baseOptions; } /** * 기본 메타데이터 생성 */ private createDefaultMetadata( tier: ServiceTier, config: Partial<BaseTemplate> ): TemplateMetadata { return { version: "1.0.0", lastModified: new Date(), author: "AgentC Team", category: config.metadata?.category || "Business", tags: config.metadata?.tags || [ "responsive", "modern", tier.toLowerCase(), ], previewImages: [], minimumTier: tier, features: this.getTierFeatures(tier), compatibility: { browsers: ["Chrome 90+", "Firefox 88+", "Safari 14+", "Edge 90+"], devices: ["Desktop", "Tablet", "Mobile"], frameworks: ["Next.js 15+", "React 18+"], }, }; } /** * 티어별 기능 목록 반환 */ private getTierFeatures(tier: ServiceTier): string[] { const baseFeatures = ["반응형 디자인", "모바일 최적화", "기본 SEO"]; if (tier === ServiceTier.STANDARD) { return [ ...baseFeatures, "고급 커스터마이제이션", "컴포넌트 토글", "추가 레이아웃", ]; } if (tier === ServiceTier.PLUS) { return [ ...baseFeatures, "완전한 커스터마이제이션", "커스텀 CSS/JS", "서드파티 통합", "고급 SEO", ]; } return baseFeatures; } /** * 티어에 맞게 컴포넌트 필터링 */ private filterComponentsForTier( components: BaseComponent[], tier: ServiceTier ): BaseComponent[] { const tierRestrictions = componentRegistry.getTierRestrictions(tier); return components.filter((component) => tierRestrictions.allowedComponents.includes(component.type) ); } /** * 티어에 맞게 커스터마이제이션 옵션 업데이트 */ private updateCustomizationOptionsForTier( options: CustomizationOptions, tier: ServiceTier ): CustomizationOptions { const newOptions = this.createDefaultCustomizationOptions(tier); // 기존 설정 중 새 티어에서 허용되는 것들만 유지 if (tier === ServiceTier.STARTER) { // Starter는 기본 설정만 허용 return newOptions; } // Standard와 Plus는 기존 설정을 최대한 유지하되 제한사항 적용 return { ...options, advanced: tier === ServiceTier.PLUS ? options.advanced : newOptions.advanced, }; } /** * 티어에 맞게 글로벌 설정 업데이트 */ private updateGlobalSettingsForTier( settings: GlobalTemplateSettings, tier: ServiceTier ): GlobalTemplateSettings { const newSettings = this.createDefaultGlobalSettings(tier); // 기존 테마 설정은 유지하되 고급 기능은 티어에 따라 제한 return { ...settings, performance: tier === ServiceTier.PLUS ? settings.performance : newSettings.performance, seo: tier === ServiceTier.STARTER ? newSettings.seo : settings.seo, }; } /** * 슬러그 생성 */ private generateSlug(name: string): string { return name .toLowerCase() .replace(/[^a-z0-9가-힣]/g, "-") .replace(/-+/g, "-") .replace(/^-|-$/g, ""); } /** * 티어별 기본 설명 반환 */ private getDefaultDescription(tier: ServiceTier): string { const descriptions = { [ServiceTier.STARTER]: "시작하기 좋은 기본 템플릿입니다. 필수 기능들로 구성되어 있습니다.", [ServiceTier.STANDARD]: "향상된 기능과 커스터마이제이션 옵션을 제공하는 표준 템플릿입니다.", [ServiceTier.PLUS]: "모든 기능과 완전한 커스터마이제이션을 제공하는 프리미엄 템플릿입니다.", }; return descriptions[tier]; } } // 싱글톤 인스턴스 export export const templateFactory = new AgentCTemplateFactory();