@restnfeel/agentc-starter-kit
Version:
한국어 기업용 CMS 모듈 - Task Master AI와 함께 빠르게 웹사이트를 구현할 수 있는 재사용 가능한 컴포넌트 시스템
299 lines (266 loc) • 7.36 kB
text/typescript
import {
CDNProvider,
CDNConfig,
loadCDNConfig,
checkCDNHealth,
} from "./cdn-config";
import { CloudflareCDNProvider } from "./providers/cloudflare-cdn";
// CDN 서비스 매니저
export class CDNService {
private provider: CDNProvider | null = null;
private config: CDNConfig;
private fallbackEnabled: boolean = true;
constructor(config?: CDNConfig) {
this.config = config || loadCDNConfig();
this.initializeProvider();
}
private initializeProvider() {
if (!this.config.enabled) {
this.provider = null;
return;
}
switch (this.config.provider) {
case "cloudflare":
this.provider = new CloudflareCDNProvider(this.config);
break;
case "aws-cloudfront":
// TODO: AWS CloudFront 프로바이더 구현
console.warn("AWS CloudFront provider not implemented yet");
this.provider = null;
break;
case "google-cloud-cdn":
// TODO: Google Cloud CDN 프로바이더 구현
console.warn("Google Cloud CDN provider not implemented yet");
this.provider = null;
break;
case "azure-cdn":
// TODO: Azure CDN 프로바이더 구현
console.warn("Azure CDN provider not implemented yet");
this.provider = null;
break;
default:
console.error(`Unknown CDN provider: ${this.config.provider}`);
this.provider = null;
}
}
// CDN 업로드 (폴백 지원)
async upload(
file: Buffer,
key: string,
mimeType: string
): Promise<{
url: string;
cdnUrl?: string;
fallback?: boolean;
}> {
if (!this.provider || !this.config.enabled) {
// CDN이 비활성화되었거나 프로바이더가 없으면 로컬 URL 반환
return {
url: `/api/media/files/${key}`,
fallback: true,
};
}
try {
const cdnUrl = await this.provider.upload(file, key, mimeType);
return {
url: cdnUrl,
cdnUrl,
fallback: false,
};
} catch (error) {
console.error("CDN upload failed, using fallback:", error);
if (this.fallbackEnabled) {
// 폴백으로 로컬 스토리지 사용
return {
url: `/api/media/files/${key}`,
fallback: true,
};
}
throw error;
}
}
// CDN에서 파일 삭제
async delete(key: string): Promise<boolean> {
if (!this.provider || !this.config.enabled) {
return false;
}
try {
await this.provider.delete(key);
return true;
} catch (error) {
console.error("CDN delete failed:", error);
return false;
}
}
// CDN 캐시 무효화
async invalidate(keys: string[]): Promise<boolean> {
if (!this.provider || !this.config.enabled || keys.length === 0) {
return false;
}
try {
await this.provider.invalidate(keys);
return true;
} catch (error) {
console.error("CDN invalidation failed:", error);
return false;
}
}
// URL 생성 (CDN 또는 폴백)
getUrl(
key: string,
options?: {
forceLocal?: boolean;
width?: number;
height?: number;
format?: string;
quality?: number;
}
): string {
if (options?.forceLocal || !this.provider || !this.config.enabled) {
return `/api/media/files/${key}`;
}
let url = this.provider.getUrl(key);
// 이미지 최적화 옵션이 있으면 적용
if (options && this.provider instanceof CloudflareCDNProvider) {
const params = new URLSearchParams();
if (options.width) params.append("w", options.width.toString());
if (options.height) params.append("h", options.height.toString());
if (options.format) params.append("f", options.format);
if (options.quality) params.append("q", options.quality.toString());
if (params.toString()) {
url += `?${params.toString()}`;
}
}
return url;
}
// CDN 상태 확인
async checkHealth(): Promise<{
healthy: boolean;
latency?: number;
error?: string;
provider: string;
}> {
const health = await checkCDNHealth(this.config);
return {
...health,
provider: this.config.provider,
};
}
// 설정 업데이트
updateConfig(newConfig: Partial<CDNConfig>) {
this.config = { ...this.config, ...newConfig };
this.initializeProvider();
}
// 폴백 활성화/비활성화
setFallbackEnabled(enabled: boolean) {
this.fallbackEnabled = enabled;
}
// 현재 설정 반환
getConfig(): CDNConfig {
return { ...this.config };
}
// 프로바이더 정보 반환
getProviderInfo(): {
name: string;
enabled: boolean;
endpoint: string;
customDomain?: string;
} {
return {
name: this.config.provider,
enabled: this.config.enabled,
endpoint: this.config.endpoint,
customDomain: this.config.customDomain,
};
}
// 캐시 헤더 생성
getCacheHeaders(mimeType: string): Record<string, string> {
if (!this.provider) {
return {};
}
return this.provider.getCacheHeaders(mimeType);
}
// 배치 작업 (여러 파일 한번에 처리)
async batchUpload(
files: Array<{
buffer: Buffer;
key: string;
mimeType: string;
}>
): Promise<
Array<{
key: string;
url: string;
cdnUrl?: string;
fallback?: boolean;
error?: string;
}>
> {
const results = await Promise.allSettled(
files.map(async ({ buffer, key, mimeType }) => {
try {
const result = await this.upload(buffer, key, mimeType);
return { key, ...result };
} catch (error) {
return {
key,
url: `/api/media/files/${key}`,
fallback: true,
error: error instanceof Error ? error.message : "Unknown error",
};
}
})
);
return results.map((result, index) => {
if (result.status === "fulfilled") {
return result.value;
} else {
return {
key: files[index].key,
url: `/api/media/files/${files[index].key}`,
fallback: true,
error:
result.reason instanceof Error
? result.reason.message
: "Upload failed",
};
}
});
}
// 배치 삭제
async batchDelete(keys: string[]): Promise<{
success: string[];
failed: string[];
}> {
const results = await Promise.allSettled(
keys.map(async (key) => {
const success = await this.delete(key);
return { key, success };
})
);
const success: string[] = [];
const failed: string[] = [];
results.forEach((result, index) => {
const key = keys[index];
if (result.status === "fulfilled" && result.value.success) {
success.push(key);
} else {
failed.push(key);
}
});
return { success, failed };
}
}
// 전역 CDN 서비스 인스턴스
let globalCDNService: CDNService | null = null;
export function getCDNService(): CDNService {
if (!globalCDNService) {
globalCDNService = new CDNService();
}
return globalCDNService;
}
// CDN 서비스 재초기화
export function reinitializeCDNService(config?: CDNConfig): CDNService {
globalCDNService = new CDNService(config);
return globalCDNService;
}