카테고리 없음

Supabase Storage에 동영상 강의를 폴더 구조로 업로드

dma-ai 2025. 9. 24. 10:59
728x90

Supabase Storage에 동영상 강의를 폴더 구조로 업로드하려면, 제공된 GitHub 예제(resumable-upload-uppy)를 기반으로 Uppy 라이브러리와 TUS 프로토콜을 활용해 resumable upload를 구현할 수 있습니다. 아래는 이를 수행하는 단계별 가이드입니다. 동영상 파일과 같은 대용량 파일을 안정적으로 업로드하기 위해 TUS 프로토콜을 사용하는 것이 적합하며, 폴더 구조를 지정하여 파일을 체계적으로 저장할 수 있습니다.


1. 프로젝트 설정 및 준비

Supabase Storage에 파일을 업로드하려면 Supabase 프로젝트와 Storage 버킷이 설정되어 있어야 합니다. 또한, Uppy와 TUS를 사용하기 위해 필요한 환경을 준비합니다.

(1) Supabase 설정

  1. Supabase 프로젝트 생성: Supabase 대시보드에서 프로젝트를 생성하고, 프로젝트 ID와 Anon Key를 확인합니다.
  2. Storage 버킷 생성: Supabase 대시보드에서 Storage 탭으로 이동해 새로운 버킷을 생성합니다(예: lectures).
  3. 업로드 정책 설정: 동영상 업로드를 허용하려면 적절한 정책을 설정해야 합니다. 예를 들어, 아래와 같은 SQL 정책을 추가합니다:
    CREATE POLICY "allow uploads" ON storage.objects
    FOR INSERT TO public
    WITH CHECK (bucket_id = 'lectures');
    이 정책은 lectures 버킷에 모든 사용자가 파일을 업로드할 수 있도록 허용합니다. 필요에 따라 인증된 사용자만 업로드하도록 제한할 수 있습니다.

(2) 프로젝트 환경 설정

  • Node.js 프로젝트 생성: 새로운 프로젝트 디렉토리를 만들고 Node.js 환경을 설정합니다.
  • 필요한 패키지 설치:
    npm install @uppy/core @uppy/react @uppy/tus @uppy/dashboard
    또는, 예제처럼 CDN을 통해 Uppy를 사용할 수도 있습니다.

2. Uppy와 TUS를 사용한 업로드 코드 작성

아래는 Supabase의 예제 코드를 기반으로 동영상 강의를 폴더 구조로 업로드하는 React 기반 코드입니다. 동영상 파일을 lectures/course1/videos/와 같은 폴더 구조로 저장하도록 설정합니다.

(1) 기본 코드 구조

index.html 또는 React 컴포넌트에서 Uppy를 설정합니다. 아래는 React 컴포넌트 예제입니다:

// FileUploader.jsx
"use client";
import React, { useState, useEffect } from "react";
import Uppy from "@uppy/core";
import { Dashboard } from "@uppy/react";
import Tus from "@uppy/tus";
import "@uppy/core/dist/style.min.css";
import "@uppy/dashboard/dist/style.min.css";

const SUPABASE_ANON_KEY = "your-anon-key"; // Supabase Anon Key
const SUPABASE_PROJECT_ID = "your-project-id"; // Supabase Project ID
const STORAGE_BUCKET = "lectures"; // Supabase Storage 버킷 이름
const BEARER_TOKEN = "your-bearer-token"; // 인증 토큰 (필요 시)
const FOLDER_PATH = "course1/videos"; // 업로드할 폴더 경로

const supabaseStorageURL = `https://${SUPABASE_PROJECT_ID}.supabase.co/storage/v1/upload/resumable`;

export default function FileUploader() {
  const [uppy, setUppy] = useState(() =>
    new Uppy({
      autoProceed: false, // 수동으로 업로드 시작
    })
      .use(Dashboard, {
        inline: true,
        target: "#drag-drop-area",
        showProgressDetails: true,
      })
      .use(Tus, {
        endpoint: supabaseStorageURL,
        headers: {
          authorization: `Bearer ${BEARER_TOKEN}`,
          apikey: SUPABASE_ANON_KEY,
        },
        uploadDataDuringCreation: true,
        chunkSize: 6 * 1024 * 1024, // 6MB 청크 크기 (Supabase 요구사항)
        allowedMetaFields: ["bucketName", "objectName", "contentType", "cacheControl"],
        onError: (error) => {
          console.error("Upload failed:", error);
        },
      })
  );

  // 파일이 추가될 때 메타데이터 설정
  uppy.on("file-added", (file) => {
    file.meta = {
      ...file.meta,
      bucketName: STORAGE_BUCKET,
      objectName: FOLDER_PATH ? `${FOLDER_PATH}/${file.name}` : file.name, // 폴더 구조 지정
      contentType: file.type, // 동영상 타입 (예: video/mp4)
      cacheControl: "3600",
    };
  });

  // 업로드 완료 시 결과 처리
  uppy.on("complete", (result) => {
    console.log("Upload complete:", result.successful);
    if (result.failed.length > 0) {
      console.error("Failed uploads:", result.failed);
    }
  });

  // 컴포넌트 언마운트 시 Uppy 정리
  useEffect(() => {
    return () => uppy.close();
  }, [uppy]);

  return (
    <div>
      <h1>Upload Video Lectures</h1>
      <div id="drag-drop-area"></div>
    </div>
  );
}

(2) 코드 설명

  • Supabase 설정값: SUPABASE_ANON_KEY, SUPABASE_PROJECT_ID, STORAGE_BUCKET, BEARER_TOKEN은 Supabase 대시보드에서 확인한 값으로 교체합니다. BEARER_TOKEN은 인증된 사용자의 경우 supabase.auth.getSession()에서 얻은 access_token을 사용할 수 있습니다.
  • 폴더 구조: FOLDER_PATH를 통해 동영상이 저장될 경로를 지정합니다. 예를 들어, course1/videos/lecture1.mp4와 같이 저장됩니다.
  • Uppy 설정:
    • Dashboard: 파일 선택 및 업로드 진행 상황을 보여주는 UI 컴포넌트입니다.
    • Tus: TUS 프로토콜을 사용해 대용량 파일을 청크 단위로 업로드합니다.
    • chunkSize: Supabase는 6MB로 설정해야 합니다.
    • allowedMetaFields: Supabase Storage에 필요한 메타데이터를 전달합니다.
  • 파일 메타데이터: file-added 이벤트에서 파일의 메타데이터를 설정해 폴더 경로와 콘텐츠 타입(예: video/mp4)을 지정합니다.

(3) HTML 기반 예제 (CDN 사용)

React 대신 HTML 파일을 사용하려면, Supabase 예제의 index.html을 수정합니다:

<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8" />
    <title>Resumable Upload Supabase + UppyJS</title>
    <link href="https://releases.transloadit.com/uppy/v3.6.1/uppy.min.css" rel="stylesheet" />
    <style>
      body {
        height: 100vh;
        display: flex;
        align-items: center;
        justify-content: center;
        flex-direction: column;
      }
      #drag-drop-area {
        margin-top: 40px;
      }
    </style>
  </head>
  <body>
    <h1>Upload Video Lectures</h1>
    <div id="drag-drop-area"></div>
    <script type="module">
      import { Uppy, Dashboard, Tus } from "https://releases.transloadit.com/uppy/v3.6.1/uppy.min.mjs";

      const SUPABASE_ANON_KEY = "your-anon-key";
      const SUPABASE_PROJECT_ID = "your-project-id";
      const STORAGE_BUCKET = "lectures";
      const BEARER_TOKEN = "your-bearer-token";
      const FOLDER_PATH = "course1/videos";
      const supabaseStorageURL = `https://${SUPABASE_PROJECT_ID}.supabase.co/storage/v1/upload/resumable`;

      const uppy = new Uppy()
        .use(Dashboard, {
          inline: true,
          target: "#drag-drop-area",
          showProgressDetails: true,
        })
        .use(Tus, {
          endpoint: supabaseStorageURL,
          headers: {
            authorization: `Bearer ${BEARER_TOKEN}`,
            apikey: SUPABASE_ANON_KEY,
          },
          uploadDataDuringCreation: true,
          chunkSize: 6 * 1024 * 1024,
          allowedMetaFields: ["bucketName", "objectName", "contentType", "cacheControl"],
          onError: (error) => {
            console.log("Failed because: " + error);
          },
        });

      uppy.on("file-added", (file) => {
        file.meta = {
          ...file.meta,
          bucketName: STORAGE_BUCKET,
          objectName: FOLDER_PATH ? `${FOLDER_PATH}/${file.name}` : file.name,
          contentType: file.type,
          cacheControl: "3600",
        };
      });

      uppy.on("complete", (result) => {
        console.log("Upload complete:", result.successful);
      });
    </script>
  </body>
</html>

3. 로컬에서 테스트

  1. 로컬 서버 실행:
    • HTML 파일을 사용하는 경우, 간단한 HTTP 서버를 실행합니다:
      npx http-server
    • React 프로젝트라면, npm run dev로 Next.js 또는 Vite 서버를 실행합니다.
  2. 파일 업로드 테스트:
    • 브라우저에서 Uppy 대시보드를 열고 동영상 파일을 선택합니다.
    • 업로드 진행 상황을 확인하고, Supabase 대시보드의 Storage 탭에서 lectures/course1/videos/ 경로에 파일이 저장되었는지 확인합니다.

4. 중요 고려사항

  • 파일 크기 제한: Supabase의 유료 플랜에서는 최대 50GB까지 업로드 가능합니다. 무료 플랜은 5GB로 제한됩니다.
  • 파일 이름 유효성: Supabase는 파일 이름에 대해 엄격한 유효성 검사를 수행합니다. 특수 문자가 포함된 파일 이름은 오류를 유발할 수 있으므로, UUID나 타임스탬프를 사용해 고유한 파일 이름을 생성하는 것이 좋습니다. 예:
    const generateFileName = (originalName) => {
      const uuid = require("uuid").v4();
      const extension = originalName.split(".").pop();
      return `${uuid}.${extension}`;
    };
    파일 추가 시:
    uppy.on("file-added", (file) => {
      const newFileName = generateFileName(file.name);
      file.meta = {
        ...file.meta,
        bucketName: STORAGE_BUCKET,
        objectName: `${FOLDER_PATH}/${newFileName}`,
        contentType: file.type,
      };
    });
  • 오류 처리: 업로드 중 404, 410, 또는 409 오류가 발생할 수 있습니다. 예를 들어:
    • 404 오류: 잘못된 엔드포인트 또는 파일 이름 문제. supabaseStorageURL과 파일 이름을 확인하세요.
    • 410 오류: 업로드 URL이 24시간 내에 만료된 경우. TUS 클라이언트는 자동으로 새 URL을 생성합니다.
    • 409 오류: 동일한 경로에 여러 클라이언트가 동시에 업로드 시 발생. 고유한 파일 이름을 사용해 방지하세요.
  • TLS 문제: 로컬 개발 환경에서 업로드가 6MB에서 멈추는 경우, supabase/config.toml에서 TLS를 비활성화하거나 올바른 인증서를 설정하세요.
  • CDN 지연: 동일한 경로에 파일을 덮어쓰면 CDN 전파로 인해 최신 파일이 즉시 반영되지 않을 수 있습니다. 새 경로에 업로드하는 것을 권장합니다.

5. 추가 팁

  • 인증 통합: 인증된 사용자의 업로드를 위해 supabase.auth.getSession()을 사용해 동적으로 BEARER_TOKEN을 설정하세요:
    const { data: { session } } = await supabase.auth.getSession();
    const BEARER_TOKEN = session.access_token;
  • 업로드 진행률 표시: Uppy의 onProgress 이벤트를 활용해 사용자에게 업로드 진행 상황을 표시할 수 있습니다:
    uppy.on("progress", (progress) => {
      console.log(`Upload progress: ${progress}%`);
    });
  • 대용량 파일 최적화: 동영상 파일은 크기가 클 수 있으므로, 항상 https://${SUPABASE_PROJECT_ID}.storage.supabase.co와 같은 직접 스토리지 호스트를 사용해 성능을 최적화하세요.

6. 결론

위 코드를 사용하면 Supabase Storage에 동영상 강의를 lectures/course1/videos/와 같은 폴더 구조로 업로드할 수 있습니다. Uppy와 TUS 프로토콜을 활용해 대용량 파일 업로드를 안정적으로 처리하며, 파일 이름 충돌이나 CDN 지연 문제를 피하려면 고유한 파일 이름과 새 경로를 사용하는 것이 좋습니다. 추가적인 오류가 발생하면 Supabase 문서()와 GitHub 이슈(,)를 참고해 디버깅하세요.

필요한 경우, 특정 오류나 추가 요구사항을 알려주시면 더 구체적으로 도와드리겠습니다!

728x90