inflearn logo
강의

강의

N
챌린지

챌린지

멘토링

멘토링

N
클립

클립

로드맵

로드맵

지식공유

[인프런 워밍업 클럽 3기] 풀스택 과정 2주차 발자국 👣

Yang HyeonBin
1

2주차에 배운 내용을 정리해본다.

깃허브 링크

 

1. 배운 내용

1. Next.js에서 메타데이터 정의하기

<meta> 태그를 이용해 사이트 정보를 정의하려면,

// page.tsx
import { Metadata } from "next";
import Ui from "./Ui";

// 페이지의 메타데이터를 정의
// use client에서는 사용 불가 - 클라이언트 코드는 Ui.tsx에서 정의하는 이유
export const metadata: Metadata = {
    title: "Dropbox Clone",
    description: "A minimalist Dropbox Clone",
};

export default function Home() {
    return <Ui />;
}

2. 파일 드랍 존 만들기

3. supabase

1. storage bucket 만들기

2. policy 생성

4. 파일 드래그앤드롭 - react-dropzone 라이브러리 사용

 

2. 이슈 사항

1. storage의 get url 형태 변경: getImageUrl 함수 커스텀 어려움

  1. bucket을 public으로 전환하고, supabase에서 제공하는 getPublicUrl 메서드를 사용

    1. storage에서 bucket 이름 옆 드롭다운 메뉴 → edit bucket → public으로 설정

       

    2. getImageUrl 함수 내부를 아래와 같이 수정

      const { data } = supabase.storage
          .from(process.env.NEXT_PUBLIC_STORAGE_BUCKET!)
          .getPublicUrl(path);
       /**
         * A simple convenience function to get the URL for an asset in a public bucket. If you do not want to use this function, you can construct the public URL by concatenating the bucket URL with the path to the asset.
         * This function does not verify if the bucket is public. If a public URL is created for a bucket which is not public, you will not be able to download the asset.
         *
         * @param path The path and name of the file to generate the public URL for. For example `folder/image.png`.
         * @param options.download Triggers the file as a download if set to true. Set this parameter as the name of the file if you want to trigger the download with a different filename.
         * @param options.transform Transform the asset before serving it to the client.
         */
        getPublicUrl(
          path: string,
          options?: { download?: string | boolean; transform?: TransformOptions }
        ): { data: { publicUrl: string } } {
          const _path = this._getFinalPath(path)
          const _queryString = []
      
          const downloadQueryParam = options?.download
            ? `download=${options.download === true ? '' : options.download}`
            : ''
      
          if (downloadQueryParam !== '') {
            _queryString.push(downloadQueryParam)
          }
      
          const wantsTransformation = typeof options?.transform !== 'undefined'
          const renderPath = wantsTransformation ? 'render/image' : 'object'
          const transformationQuery = this.transformOptsToQueryString(options?.transform || {})
      
          if (transformationQuery !== '') {
            _queryString.push(transformationQuery)
          }
      
          let queryString = _queryString.join('&')
          if (queryString !== '') {
            queryString = `?${queryString}`
          }
      
          return {
            data: { publicUrl: encodeURI(`${this.url}/${renderPath}/public/${_path}${queryString}`) },
          }
        }
      
  2. StorageFileApi.ts를 참고하면 다양한 메서드가 있음 - createSingedUrl를 이용하면 expiresIn을 직접 지정 가능. 이걸 이용하면 bucket이 public이 아니어도 가능할듯

     /**
       * Creates a signed URL. Use a signed URL to share a file for a fixed amount of time.
       *
       * @param path The file path, including the current file name. For example `folder/image.png`.
       * @param expiresIn The number of seconds until the signed URL expires. For example, `60` for a URL which is valid for one minute.
       * @param options.download triggers the file as a download if set to true. Set this parameter as the name of the file if you want to trigger the download with a different filename.
       * @param options.transform Transform the asset before serving it to the client.
       */
      async createSignedUrl(
        path: string,
        expiresIn: number,
        options?: { download?: string | boolean; transform?: TransformOptions }
      ): Promise<
        | {
            data: { signedUrl: string }
            error: null
          }
        | {
            data: null
            error: StorageError
          }
      > {
        try {
          let _path = this._getFinalPath(path)
    
          let data = await post(
            this.fetch,
            `${this.url}/object/sign/${_path}`,
            { expiresIn, ...(options?.transform ? { transform: options.transform } : {}) },
            { headers: this.headers }
          )
          const downloadQueryParam = options?.download
            ? `&download=${options.download === true ? '' : options.download}`
            : ''
          const signedUrl = encodeURI(`${this.url}${data.signedURL}${downloadQueryParam}`)
          data = { signedUrl }
          return { data, error: null }
        } catch (error) {
          if (isStorageError(error)) {
            return { data: null, error }
          }
    
          throw error
        }
      }
    
      /**
       * Creates multiple signed URLs. Use a signed URL to share a file for a fixed amount of time.
       *
       * @param paths The file paths to be downloaded, including the current file names. For example `['folder/image.png', 'folder2/image2.png']`.
       * @param expiresIn The number of seconds until the signed URLs expire. For example, `60` for URLs which are valid for one minute.
       * @param options.download triggers the file as a download if set to true. Set this parameter as the name of the file if you want to trigger the download with a different filename.
       */
      async createSignedUrls(
        paths: string[],
        expiresIn: number,
        options?: { download: string | boolean }
      ): Promise<
        | {
            data: { error: string | null; path: string | null; signedUrl: string }[]
            error: null
          }
        | {
            data: null
            error: StorageError
          }
      > {
        try {
          const data = await post(
            this.fetch,
            `${this.url}/object/sign/${this.bucketId}`,
            { expiresIn, paths },
            { headers: this.headers }
          )
    
          const downloadQueryParam = options?.download
            ? `&download=${options.download === true ? '' : options.download}`
            : ''
          return {
            data: data.map((datum: { signedURL: string }) => ({
              ...datum,
              signedUrl: datum.signedURL
                ? encodeURI(`${this.url}${datum.signedURL}${downloadQueryParam}`)
                : null,
            })),
            error: null,
          }
        } catch (error) {
          if (isStorageError(error)) {
            return { data: null, error }
          }
    
          throw error
        }
      }
    
    

2. server action 파일에서 console.log는 개발자 도구가 아닌 터미널에 찍힌다는 사실..

3. 파일명에 한글이 포함될 경우 업로드 안되는 오류

  1. 어떻게 저장할까?

    • base64 인코딩을 통해 S3-safe한 이름으로 변경해 저장하면 업로드 가능

    ⇒ 저장 및 다운로드, 이름 표시하는 코드에서 인코딩/디코딩 함수를 사용하게 변경 완료

  2. 어떻게 검색할까?

    • 인코딩/디코딩된 값으로 검색 호환이 안됨

    ⇒ db에 저장해야 함

  3. db에 저장하도록 변경했는데, 한글 초성만 검색됨..

    • todo-list 검색 때와의 차이가 뭐길래 안되는거지?

 

3. 미션 - 파일의 마지막 수정(업로드) 시간 표시하기

웹 개발 Next.js Supabase React.js File

답변 0