@restnfeel/agentc-starter-kit
Version:
한국어 기업용 CMS 모듈 - Task Master AI와 함께 빠르게 웹사이트를 구현할 수 있는 재사용 가능한 컴포넌트 시스템
588 lines (533 loc) • 17.5 kB
text/typescript
/**
* 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();