콘텐츠 가져오기

이 페이지

EmDash의 가져오기 시스템은 플러그 가능한 소스 아키텍처를 사용합니다. 각 소스는 특정 플랫폼에서 콘텐츠를 탐색, 분석, 가져오는 방법을 알고 있습니다.

가져오기 소스

소스 ID플랫폼탐색OAuth전체 가져오기
wxrWordPress 내보내기 파일아니오아니오
wordpress-comWordPress.com
wordpress-rest자체 호스팅 WordPress아니오탐색만

WXR 파일 업로드

가장 완전한 가져오기 방법입니다. WordPress eXtended RSS(WXR) 내보내기 파일을 관리 대시보드에 직접 업로드합니다.

기능:

  • 모든 글 타입(사용자 정의 포함)
  • 모든 메타 필드
  • 초안 및 비공개 글
  • 전체 택소노미 계층
  • 미디어 첨부 메타데이터

WXR 파일 얻는 방법:

  1. WordPress 관리자에서 도구 → 내보내기로 이동합니다
  2. 모든 콘텐츠 또는 특정 글 타입을 선택합니다
  3. 내보내기 파일 다운로드를 클릭합니다
  4. .xml 파일을 EmDash에 업로드합니다

WordPress.com OAuth

WordPress.com에 호스팅된 사이트의 경우 수동 파일 내보내기 없이 OAuth로 연결해 가져올 수 있습니다.

  1. WordPress.com 사이트 URL을 입력합니다
  2. Connect with WordPress.com을 클릭합니다
  3. WordPress.com 팝업에서 EmDash를 승인합니다
  4. 가져올 콘텐츠를 선택합니다

포함 항목:

  • 게시 및 초안 콘텐츠
  • 비공개 글(인증 포함)
  • API를 통한 미디어 파일
  • REST API에 노출된 사용자 정의 필드

WordPress REST API 탐색

URL을 입력하면 EmDash가 사이트를 탐색해 WordPress를 감지하고 사용 가능한 콘텐츠를 보여줍니다:

감지: WordPress 6.4
├── 게시물: 127 (게시됨)
├── 페이지: 12 (게시됨)
└── 미디어: 89개 파일

참고: 초안과 비공개 콘텐츠는 인증이나
WXR 전체 내보내기가 필요합니다.

REST 탐색은 정보 제공용입니다. 완전한 가져오기를 위해 WXR 파일 업로드 또는 OAuth 연결(WordPress.com)을 제안합니다.

가져오기 흐름

모든 소스는 동일한 흐름을 따릅니다:

┌─────────────┐     ┌─────────────┐     ┌─────────────┐     ┌─────────────┐
│   연결      │────▶│   분석      │────▶│   준비      │────▶│   실행      │
│  (탐색/    │     │  (스키마    │     │  (스키마    │     │  (콘텐츠   │
│   업로드)   │     │   확인)     │     │   생성)     │     │   가져오기) │
└─────────────┘     └─────────────┘     └─────────────┘     └─────────────┘

1단계: 연결

URL을 입력해 탐색하거나 파일을 직접 업로드합니다.

URL 탐색은 등록된 모든 소스를 병렬로 실행합니다. 가장 높은 신뢰도 일치가 다음 동작을 결정합니다:

  • WordPress.com 사이트 → OAuth 연결 제안
  • 자체 호스팅 WordPress → 내보내기 안내 표시
  • 알 수 없음 → 파일 업로드 제안

2단계: 분석

소스가 콘텐츠를 파싱하고 스키마 호환성을 확인합니다:

글 타입:
├── post (127) → posts [새 컬렉션]
├── page (12)  → pages [기존, 호환]
├── product (45) → products [필드 3개 추가]
└── revision (234) → [건너뜀 - 내부 타입]

필요한 스키마 변경:
├── 컬렉션 생성: posts
├── pages에 필드 추가: featured_image
└── 컬렉션 생성: products

각 글 타입의 상태:

상태의미
준비됨컬렉션이 호환 가능한 필드와 함께 존재
새 컬렉션자동으로 생성됨
필드 추가컬렉션 존재, 누락 필드 추가
비호환필드 타입 충돌(수동 수정 필요)

3단계: 스키마 준비

Create Schema & Import를 클릭하면:

  1. SchemaRegistry를 통해 새 컬렉션 생성
  2. 올바른 열 타입으로 누락 필드 추가
  3. 인덱스가 있는 콘텐츠 테이블 설정

4단계: 가져오기 실행

콘텐츠가 순차적으로 가져옵니다:

  • Gutenberg/HTML이 Portable Text로 변환
  • WordPress 상태가 EmDash 상태에 매핑
  • WordPress 작성자가 소유권(authorId)과 표시용 바이라인에 매핑
  • 택소노미가 생성되고 연결
  • 재사용 블록(wp_block)이 섹션으로 가져옴
  • 실시간 진행 상황 표시

작성자 가져오기 동작:

  • 작성자 매핑이 EmDash 사용자를 가리키면 소유권이 해당 사용자로 설정되고 동일 사용자를 위한 연결된 바이라인이 생성/재사용됩니다.
  • 사용자 매핑이 없으면 WordPress 작성자 정보에서 게스트 바이라인이 생성/재사용됩니다.
  • 가져온 항목은 순서가 있는 바이라인 크레딧을 받으며 첫 번째 크레딧이 primaryBylineId로 설정됩니다.

5단계: 미디어 가져오기(선택)

콘텐츠 후 선택적으로 미디어를 가져옵니다:

  1. 분석 — 유형별 첨부 파일 수 표시

    발견된 미디어:
    ├── 이미지: 75개 파일
    ├── 동영상: 10개 파일
    └── 기타: 4개 파일
  2. 다운로드 — WordPress URL에서 진행 상황과 함께 스트리밍

    미디어 가져오기 중...
    ├── 89개 중 45개 (50%)
    ├── 현재: vacation-photo.jpg
    └── 상태: 업로드 중
  3. URL 재작성 — 새 URL로 콘텐츠 자동 업데이트

미디어 가져오기는 콘텐츠 해싱(xxHash64)으로 중복을 제거합니다. 여러 글에서 사용된 같은 이미지는 한 번만 저장됩니다.

소스 인터페이스

가져오기 소스는 표준 인터페이스를 구현합니다:

interface ImportSource {
	/** 고유 식별자 */
	id: string;

	/** 표시 이름 */
	name: string;

	/** URL 탐색(선택) */
	probe?(url: string): Promise<SourceProbeResult | null>;

	/** 이 소스의 콘텐츠 분석 */
	analyze(input: SourceInput, context: ImportContext): Promise<ImportAnalysis>;

	/** 콘텐츠 항목 스트리밍 */
	fetchContent(input: SourceInput, options: FetchOptions): AsyncGenerator<NormalizedItem>;
}

입력 타입

소스는 다양한 입력 타입을 받습니다:

// 파일 업로드(WXR)
{ type: "file", file: File }

// 선택적 토큰이 있는 URL(REST API)
{ type: "url", url: string, token?: string }

// OAuth 연결(WordPress.com)
{ type: "oauth", url: string, accessToken: string }

정규화된 출력

모든 소스는 동일한 정규화 형식을 생성합니다:

interface NormalizedItem {
	sourceId: string | number;
	postType: string;
	status: "publish" | "draft" | "pending" | "private" | "future";
	slug: string;
	title: string;
	content: PortableTextBlock[];
	excerpt?: string;
	date: Date;
	author?: string;
	authors?: string[];
	categories?: string[];
	tags?: string[];
	meta?: Record<string, unknown>;
	featuredImage?: string;
}

API 엔드포인트

가져오기 시스템은 다음 엔드포인트를 노출합니다:

URL 탐색

POST /_emdash/api/import/probe
Content-Type: application/json

{ "url": "https://example.com" }

감지된 플랫폼과 제안 동작을 반환합니다.

WXR 분석

POST /_emdash/api/import/wordpress/analyze
Content-Type: multipart/form-data

file: [WordPress 내보내기 .xml]

스키마 호환성과 함께 글 타입 분석을 반환합니다.

스키마 준비

POST /_emdash/api/import/wordpress/prepare
Content-Type: application/json

{
  "postTypes": [
    { "name": "post", "collection": "posts", "enabled": true }
  ]
}

컬렉션과 필드를 생성합니다.

가져오기 실행

POST /_emdash/api/import/wordpress/execute
Content-Type: multipart/form-data

file: [WordPress 내보내기 .xml]
config: { "postTypeMappings": { "post": { "collection": "posts" } } }

지정된 컬렉션으로 콘텐츠를 가져옵니다.

미디어 가져오기

POST /_emdash/api/import/wordpress/media
Content-Type: application/json

{
  "attachments": [{ "id": 123, "url": "https://..." }],
  "stream": true
}

다운로드/업로드 중 NDJSON 진행 업데이트를 스트리밍합니다.

URL 재작성

POST /_emdash/api/import/wordpress/rewrite-urls
Content-Type: application/json

{
  "urlMap": { "https://old.com/image.jpg": "/_emdash/media/abc123" }
}

새 미디어 URL로 Portable Text 콘텐츠를 업데이트합니다.

오류 처리

복구 가능한 오류

  • 네트워크 타임아웃 — 백오프로 재시도
  • 단일 항목 파싱 실패 — 로그 남기고 건너뜀, 가져오기 계속
  • 미디어 다운로드 실패 — 수동 처리 필요로 표시

치명적 오류

  • 잘못된 파일 형식 — 오류 메시지와 함께 가져오기 중단
  • 데이터베이스 연결 끊김 — 가져오기 일시 정지, 재개 허용
  • 스토리지 용량 초과 — 가져오기 중단, 사용량 표시

오류 보고서

가져오기 후:

가져오기 완료

✓ 125개 게시물 가져옴
✓ 12개 페이지 가져옴
✓ 85개 미디어 참조 기록

⚠ 2개 항목 경고:
  - 게시물 "Special Characters ñ" - 제목 인코딩 수정
  - 페이지 "About" - 중복 slug가 "about-1"로 변경

✗ 1개 항목 실패:
  - 게시물 ID 456 - 콘텐츠 파싱 오류(초안으로 저장)

실패한 항목은 검토를 위해 _importError에 원본 콘텐츠가 포함된 초안으로 저장됩니다.

사용자 정의 소스 만들기

다른 플랫폼용 소스를 만듭니다:

import type { ImportSource } from "emdash/import";

export const mySource: ImportSource = {
	id: "my-platform",
	name: "My Platform",
	description: "Import from My Platform",
	icon: "globe",
	canProbe: true,

	async probe(url) {
		// URL이 플랫폼과 일치하는지 확인
		const response = await fetch(`${url}/api/info`);
		if (!response.ok) return null;

		return {
			sourceId: "my-platform",
			confidence: "definite",
			detected: { platform: "my-platform" },
			// ...
		};
	},

	async analyze(input, context) {
		// 콘텐츠 파싱 및 분석
		// ImportAnalysis 반환
	},

	async *fetchContent(input, options) {
		// 각 콘텐츠에 대해 NormalizedItem yield
		for (const item of items) {
			yield {
				sourceId: item.id,
				postType: "post",
				title: item.title,
				content: convertToPortableText(item.body),
				// ...
			};
		}
	},
};

EmDash 구성에 소스를 등록합니다:

import { mySource } from "./src/import/custom-source";

export default defineConfig({
	integrations: [
		emdash({
			import: {
				sources: [mySource],
			},
		}),
	],
});

다음 단계