[인프런 워밍업 클럽 3기] 풀스택 과정 2주차 발자국 👣
2주차에 배운 내용을 정리해본다.깃허브 링크 1. 배운 내용1. Next.js에서 메타데이터 정의하기<meta> 태그를 이용해 사이트 정보를 정의하려면,서버 컴포넌트 파일에서Metadata를 정의해줘야 함// 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. 파일 드랍 존 만들기<input /> 태그를 이용, type="file"3. supabase1. storage bucket 만들기업로드 가능한 파일 종류 설정 가능만들 때 Allowed MIME types 옵션에서 image/* 등의 조건을 추가하면 됨2. policy 생성사이드바 policy 메뉴에서 생성 가능이름, 가능한 액션 종류 선택, 누구에게 가능하게 할지 선택 가능4. 파일 드래그앤드롭 - react-dropzone 라이브러리 사용사용법은 npm 공식 문서의 코드 조각을 확인https://www.npmjs.com/package/react-dropzone 2. 이슈 사항1. storage의 get url 형태 변경: getImageUrl 함수 커스텀 어려움이미지의 만료일을 지정할 수 있게 변경됨그러면서 token이라는 서치 파라미터가 필수값으로 추가된 듯토큰을 누락한 형태로 확인 시 에러가 발생하며 이미지 로드 실패{"statusCode":"400","error":"Error","message":"querystring must have required property 'token'"} bucket을 public으로 전환하고, supabase에서 제공하는 getPublicUrl 메서드를 사용storage에서 bucket 이름 옆 드롭다운 메뉴 → edit bucket → public으로 설정 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}`) }, } } 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는 개발자 도구가 아닌 터미널에 찍힌다는 사실..storageActions.ts에 정의한 uploadFile 함수를 수정하고 제대로 데이터가 들어가는지 확인하려고 console.log를 사용아무리 해도 useQuery까지는 잘 로그가 찍히는데, uploadFile에 작성한 로그가 브라우저 개발자도구에 전혀 찍히지 않아 뭐가 문제인지 한참 헤맴코드 에디터 터미널에 찍히고 있었음.. ㅠㅠ3. 파일명에 한글이 포함될 경우 업로드 안되는 오류디스코드에 다른 러너분들이 공유해준 정보에 따르면 supabase storage는 AWS의 S3 스토리지와 호횐되어, 파일명도 AWS S3의 Safe charaters - 영문, 숫자, 일부 특수기호 만 허용한다고 함어떻게 저장할까?base64 인코딩을 통해 S3-safe한 이름으로 변경해 저장하면 업로드 가능⇒ 저장 및 다운로드, 이름 표시하는 코드에서 인코딩/디코딩 함수를 사용하게 변경 완료어떻게 검색할까?인코딩/디코딩된 값으로 검색 호환이 안됨⇒ db에 저장해야 함db에 저장하도록 변경했는데, 한글 초성만 검색됨..todo-list 검색 때와의 차이가 뭐길래 안되는거지? 3. 미션 - 파일의 마지막 수정(업로드) 시간 표시하기storage를 확인하면, Added on, Last modified 정보가 있음file이 어떤 형태인지 로그 찍어 확인file.updated_at 키에 저장된 값임을 확인, 이 값을 사용추가로 file.metadata에 파일 타입과 사이즈 등의 정보도 있어 그 정보들도 적절히 표시하도록 함 (ui는 supabase storage를 참고함)