CTMM MEDIA

MEDIA API 1.3.0 · 사진과 짧은 영상

폴더는 자유롭게 정리하고,
파일 주소는 그대로 쓰세요.

서비스별로 파일을 나누고, 익숙한 폴더 경로로 업로드·조회·이동합니다. 논리 경로를 바꾸어도 파일의 고유 식별자와 다운로드 주소, 원본과 RAM 사본은 유지됩니다.

미디어 보관함 열기Swagger에서 API 확인JavaScript 클라이언트 받기
서비스마다 분리된 관리

API 키에 허용된 namespace만 조회하고 변경합니다. 요청 본문에 다른 서비스 이름을 넣어도 권한이 생기지 않습니다.

이름과 주소의 독립

관리 경로는 namespace + path, 실제 저장과 다운로드는 id를 기준으로 합니다. 이동 시 다시 업로드하거나 변환하지 않습니다.

영속 보관 + RAM 제어

원본은 SeaweedFS, 게시 파일은 SSD, RAM은 추가 사본입니다. RAM 해제가 원본 삭제로 이어지지 않습니다.

어떤 정보를 저장하나요?

Wedding 서비스에서는 업체와 용도별로 경로를 나눌 수 있습니다. 한국어와 공백을 사용할 수 있으며, 사진과 영상 모두 같은 분류 체계를 사용합니다.

namespace: wedding

업체/
  123/
    대표사진/
      정면.jpeg
    갤러리/
      실내 사진.jpeg
      야외 사진.jpeg
    영상/
      소개 영상.mp4
항목사용하는 곳
namespace서비스 구분과 API 키의 관리 범위
path파일 분류·검색·폴더 이동
id변경되지 않는 서버 발급 식별자
/m/{id}/detail.webp경로 변경 뒤에도 유지되는 다운로드 주소

논리 경로를 운영체제 파일 경로로 직접 사용하지 않습니다. 같은 서비스 안의 같은 경로를 덮어쓰지 않으며, 중복이면 409 Conflict를 반환합니다.

1. 지정한 서비스와 경로에 업로드하기

백엔드에서 사용자의 업무 권한을 확인한 다음 업로드 예약을 만듭니다. API 키는 백엔드에만 보관하세요. 업로드 주소와 관리 토큰은 서로 다른 용도입니다.

import { MediaClient, MediaApiError } from './media-client.mjs';

const media = new MediaClient({
  baseUrl: 'https://media.ctmm.cocifee.com',
  apiKey: process.env.WEDDING_MEDIA_API_KEY,
});

const ticket = await media.createUpload({
  namespace: 'wedding',
  path: '업체/123/갤러리/실내 사진.jpeg',
  contentType: 'image/jpeg',
  visibility: 'public',
  cachePolicy: 'manual',
});
// ticket에는 id, namespace, path, uploadUrl, expiresIn,
// statusUrl, downloadUrls가 들어 있습니다.

브라우저에서는 발급받은 업로드 주소로 본문만 전송합니다.

import { uploadWithTicket } from './media-client.mjs';
await uploadWithTicket(ticket, fileInput.files[0]);

백엔드에서 변환 완료 후 파일 주소를 얻습니다.

const ready = await media.waitUntilReady(ticket.id);
const imageUrl = ready.variants['detail.webp'];
// 영상은 playback.mp4와 poster.webp를 사용합니다.
// 비공개 파일은 아래처럼 만료 시간이 있는 주소를 발급합니다.
const privateAccess = await media.access(ticket.id, 60);

예약을 받았다고 변환본이 바로 준비된 것은 아닙니다. ready 상태를 확인하세요. 기본 공개 범위는 비공개이며, 비공개 파일은 논리 경로를 안다고 다운로드할 수 없습니다.

2. 필요한 폴더만 페이지 단위로 조회하기

바로 아래 폴더와 파일을 각각 조회합니다. 전체 파일을 브라우저에 받아 분류하지 않습니다. 두 목록은 각각 독립적인 다음 페이지 커서를 가집니다.

const folders = await media.folders({
  namespace: 'wedding', folder: '업체/123/', limit: 24,
});
const files = await media.list({
  namespace: 'wedding', folder: '업체/123/',
  recursive: false, limit: 24,
});

// 하위 폴더까지 파일 검색: recursive를 true로 지정합니다.
const matches = await media.list({
  namespace: 'wedding', folder: '업체/123/', recursive: true,
  q: '실내', kind: 'image', visibility: 'public', status: 'ready',
  limit: 24,
});

if (files.nextCursor) {
  const next = await media.list({
    namespace: 'wedding', folder: '업체/123/', recursive: false,
    limit: 24, cursor: files.nextCursor,
  });
}

기본 24개, 최대 50개씩 반환합니다. 업체/123/에 업체/1234/가 섞이지 않습니다. 검색 조건이나 폴더를 바꾸면 커서도 버리고 첫 페이지부터 조회하세요.

3. 파일 이름과 폴더 위치 바꾸기

await media.rename(ticket.id, '업체/123/대표사진/메인 사진.jpeg');

await media.moveFolder({
  namespace: 'wedding',
  source: '업체/123',
  destination: '보관/업체123',
});
// 기존 imageUrl, id, 공개 범위, 캐시 정책과 RAM 사본은 유지됩니다.

폴더 전체 이동은 최대 5,000개 기록을 하나의 데이터베이스 트랜잭션으로 처리합니다. 대상 폴더가 이미 있거나 경로가 충돌하면 일부만 이동하지 않고 전체를 거절합니다. 서비스 사이의 소유권 이동과 기존 폴더에 합치는 기능은 제공하지 않습니다.

try {
  await media.rename(ticket.id, '이미 사용 중인 경로.jpeg');
} catch (error) {
  if (error instanceof MediaApiError && error.status === 409) {
    // 기존 파일은 유지됩니다. 다른 경로를 선택하게 안내하세요.
  } else {
    throw error;
  }
}

4. 영속 원본을 유지하면서 RAM 제어하기

SeaweedFS 원본·변환본→SSD 게시 파일→선택적인 RAM 사본
await media.setCachePolicy(ticket.id, 'manual');
const admission = await media.warmCache(ticket.id, ['detail.webp']);
const cache = await media.cache(ticket.id);
// variants의 resident, queued, serving, reason을 확인합니다.
await media.evictCache(ticket.id, 'detail.webp');

// 선택 파일: 한 요청에 최대 20개입니다.
const batch = await media.cacheBatch([ticket.id], 'warm');

// 폴더도 한 번에 최대 20개만 평가합니다.
const first = await media.folderCache({
  namespace: 'wedding', folder: '업체/123/', action: 'warm', limit: 20,
});
// nextCursor가 있어도 다음 묶음은 자동 실행하지 않습니다.

auto는 다운로드 시 적재를 요청하고, manual은 명시적 요청만 허용합니다. disabled는 SSD 전용입니다. 적재 요청의 접수와 실제 완료는 다릅니다. 파일별 결과와 거절 이유를 확인하세요.

RAM 해제는 원본·SSD·캐시 정책을 유지합니다. 자동 정책이면 다음 다운로드나 미리보기에서 다시 적재될 수 있습니다. 기존 64MiB 파일 제한과 메모리 보호 조건은 수동 요청에도 적용합니다.

관리 화면은 영속 원본·변환본 용량과 RAM 사본 용량을 구분합니다. RAM 폴더 합계는 약 30초 간격의 측정 시각과 함께 표시하며, 개별 파일 상태는 따로 확인할 수 있습니다.

5. 로그인 없는 화면과 서비스 관리 권한

미디어 보관함은 계속 로그인 없이 열립니다. 기존 파일은 소유 서비스를 추측하지 않고 legacy 영역으로 분류했습니다. 이 영역의 파일명·경로·상태는 기존 정책대로 보이며 비공개 본문은 공개하지 않습니다. 익명 RAM 조작은 이 영역의 공개 파일만 가능합니다.

새 Wedding 파일의 경로 조회, 이름 변경, 폴더 이동에는 해당 서비스의 관리 토큰을 연결합니다. 백엔드에서 다음처럼 발급한 뒤 신뢰하는 관리자에게 전달하세요.

// 백엔드에서 실행합니다. 유효 시간은 15분입니다.
const session = await media.createAdminSession('wedding');
// /admin의 ‘서비스 관리 토큰’에 session.token을 연결합니다.
// API 키 자체를 붙여 넣지 마세요.

관리 토큰은 한 서비스 범위로 제한되며 탭의 메모리에만 보관합니다. 비공개 파일 미리보기는 그 권한으로 발급한 짧은 서명 주소를 사용합니다. 파일을 관리할 권한을 위임하는 토큰이므로 외부에 공개하지 마세요.

입력 규칙과 기존 클라이언트

목록 정렬 변경, 예약 해제의 상세 규칙, 데이터베이스 인덱스와 조회 비용, 데이터 이전·되돌리기 절차는 개발자 상세 안내에서 확인하세요.