Ecosyste.ms: Awesome

An open API service indexing awesome lists of open source software.

Awesome Lists | Featured Topics | Projects

https://github.com/1-blue/deprecated-blequotes

๐ŸŽฌ ์˜ํ™” / ๋“œ๋ผ๋งˆ / ๋„์„œ ๋ช…๋Œ€์‚ฌ๋ฅผ ์ž‘์„ฑํ•˜๋Š” ๊ฐ„๋‹จํ•œ ์‚ฌ์ดํŠธ ๐ŸŽฌ
https://github.com/1-blue/deprecated-blequotes

aws-s3 express prisma react redux-toolkit tailwindcss typescript

Last synced: 1 day ago
JSON representation

๐ŸŽฌ ์˜ํ™” / ๋“œ๋ผ๋งˆ / ๋„์„œ ๋ช…๋Œ€์‚ฌ๋ฅผ ์ž‘์„ฑํ•˜๋Š” ๊ฐ„๋‹จํ•œ ์‚ฌ์ดํŠธ ๐ŸŽฌ

Awesome Lists containing this project

README

        

# ๐Ÿ˜ถ ๋ช…๋Œ€์‚ฌ ์ปค๋ฎค๋‹ˆํ‹ฐ
์˜ํ™” / ๋“œ๋ผ๋งˆ / ๋„์„œ์˜ ๋ช…๋Œ€์‚ฌ๋ฅผ ๊ฐ„๋‹จํ•˜๊ฒŒ ์ž‘์„ฑํ•˜๋Š” ์ปค๋ฎค๋‹ˆํ‹ฐ์ž…๋‹ˆ๋‹ค.

๐Ÿง‘โ€๐Ÿ’ป ์‚ฌ์šฉํ•œ ๊ธฐ์ˆ  ๐Ÿง‘โ€๐Ÿ’ป












๐Ÿ”จ ์‚ฌ์šฉ ํˆด ๐Ÿ”จ








# ๐Ÿ•น๏ธ ๊ตฌํ˜„ ๊ธฐ๋Šฅ
1. [`Movie DB API`](https://developers.themoviedb.org/3)๋ฅผ ์ด์šฉํ•œ ์˜ํ™” ๋ฐ ๋“œ๋ผ๋งˆ๋“ค์˜ ๊ฐ์ข… ์ •๋ณด ํŒจ์น˜ ๋ฐ ๊ฒ€์ƒ‰
2. [`Kakao Book API`](https://developers.kakao.com/docs/latest/ko/daum-search/dev-guide#search-book)๋ฅผ ์ด์šฉํ•œ ๋„์„œ ๊ฒ€์ƒ‰
3. `Image Carousel` ( `react-slick` ์‚ฌ์šฉ )
4. ๋ช…๋Œ€์‚ฌ ๋“ฑ๋ก ๊ธฐ๋Šฅ ( [`AWS-S3`์˜ `presignedURL`](https://1-blue.github.io/posts/AWS-S3-presignedURL)๋ฅผ ์ด์šฉํ•œ ์ด๋ฏธ์ง€ ๋“ฑ๋ก )
5. ๋ช…๋Œ€์‚ฌ์— ์ข‹์•„์š” ๋ฐ ์‹ซ์–ด์š” ๊ธฐ๋Šฅ
6. ์˜ํ™” / ๋“œ๋ผ๋งˆ / ๋„์„œ ๊ฒ€์ƒ‰ ๊ธฐ๋Šฅ ( [`Debouncing`](https://1-blue.github.io/posts/%EB%94%94%EB%B0%94%EC%9A%B4%EC%8B%B1-%EC%93%B0%EB%A1%9C%ED%8B%80%EB%A7%81-%EB%A9%94%EB%AA%A8%EC%9D%B4%EC%A0%9C%EC%9D%B4%EC%85%98/#%EF%B8%8F-%EB%94%94%EB%B0%94%EC%9A%B4%EC%8B%B1--debouncing-) ์‚ฌ์šฉ )
7. ๋ฌดํ•œ ์Šคํฌ๋กค๋ง ( [`IntersectionObserver API`](https://1-blue.github.io/posts/Intersection-Observer-API) ์‚ฌ์šฉ )
8. [`Redux-ToolKit`](https://1-blue.github.io/posts/Redux-Toolkit)์„ ์ด์šฉํ•œ ์ „์—ญ ์ƒํƒœ ๊ด€๋ฆฌ
9. [`Prisma`](https://1-blue.github.io/posts/prisma)๋ฅผ ์ด์šฉํ•œ `DB`๊ด€๋ฆฌ ๋ฐ ๋ฐ์ดํ„ฐ ๊ด€๋ฆฌ
10. [AWS-EC2 + nginx + certbot์„ ์ด์šฉํ•œ ๋ฐฐํฌ](https://1-blue.github.io/posts/deploy)

# ๐Ÿ€ ์ œ์ž‘ ํ™˜๊ฒฝ
1. OS: `Window11`
2. editor: `VSCode`, `Sourcetree` ( + `Notion`, `Figma` )
3. terminal: `git bash`
4. Database: `Mysql`
6. vcs: `Git` / `GitHub`
7. Front: `React.js`
8. Back: `Node`์˜ `Express`
9. ์ด๋ฏธ์ง€ ์ €์žฅ์†Œ: `AWS S3`
10. ๋ฐฐํฌ: `AWS-EC2`

# ๐Ÿ“ ๊ฐ€์ด๋“œ๋ผ์ธ
## ๐Ÿ™‚ ํ”„๋ก ํŠธ ์—”๋“œ
### 0๏ธโƒฃ ์ข…์†์„ฑ ์„ค์น˜
```bash
npm install
```

### 1๏ธโƒฃ ํ™˜๊ฒฝ ๋ณ€์ˆ˜ ๋“ฑ๋ก
```
# ์„œ๋ฒ„ URL ( ์ƒํ™ฉ์— ๋งž๊ฒŒ ์ˆ˜์ • )
REACT_APP_API_URL=http://localhost:3050

# "MovieDB" ์ด๋ฏธ์ง€ URL
REACT_APP_MOVIE_IMAGE_URL=https://image.tmdb.org/t/p/w500
```

### 2๏ธโƒฃ ํ…Œ์ŠคํŠธ ์‹คํ–‰
```bash
npm start
```

### 3๏ธโƒฃ ๋นŒ๋“œ
```bash
npm run build
```

## ๐Ÿ™ƒ ๋ฐฑ์—”๋“œ
### 0๏ธโƒฃ ์ข…์†์„ฑ ์„ค์น˜
```bash
cd backend
npm install
```

### 1๏ธโƒฃ ํ™˜๊ฒฝ ๋ณ€์ˆ˜ ๋“ฑ๋ก
```
# ํ™˜๊ฒฝ ๋ณ€์ˆ˜
NODE_ENV=development

# "Prisma"์—์„œ "Mysql" ์—ฐ๊ฒฐ์„ ์œ„ํ•œ
DATABASE_URL="mysql://<์œ ์ €์ด๋ฆ„>:<๋น„๋ฐ€๋ฒˆํ˜ธ>@localhost:3306/"

# "MovieDB" ์š”์ฒญ ๊ด€๋ จ
MOVIE_DB_API_URL=https://api.themoviedb.org/3
MOVIE_DB_API_KEY=

# "Kakao Book" ์š”์ฒญ ๊ด€๋ จ
KAKAO_API_URL=https://dapi.kakao.com
KAKAO_API_KEY=

# "AWS-S3" ๊ด€๋ จ
AWS_BUCKET=<๋ฒ„ํ‚ท๋ช…>
AWS_REGION=<์‚ฌ์šฉ์ง€์—ญ๋ช…>
AWS_ACCESS_KEY=
AWS_ACCESS_SECRET_KEY=
```

### 2๏ธโƒฃ ํ…Œ์ŠคํŠธ ์‹คํ–‰
```bash
npm run dev
```

### 3๏ธโƒฃ prisma ๋ช…๋ น์–ด
```bash
# ๊ฐ€์งœ ๋ฐ์ดํ„ฐ ๋“ฑ๋ก
npx prisma seed

# ๋ฐ์ดํ„ฐ ๋ฒ ์ด์Šค ์ดˆ๊ธฐํ™”
npx prisma migrate dev

# ๋ฐ์ดํ„ฐ ๋ฒ ์ด์Šค ์ดˆ๊ธฐํ™” ๋ฐ ๊ฐ€์งœ ๋ฐ์ดํ„ฐ ๋“ฑ๋ก
npx prisma migrate reset
```

# โœ๏ธ ํ”„๋กœ์ ํŠธ์™€ ๊ด€๋ จ๋œ ํฌ์ŠคํŠธ๋“ค
1. [`Redux`](https://1-blue.github.io/posts/Redux/)
2. [`React` ์Šคํฌ๋กค ๋ฐฉํ–ฅ ์ฐพ๊ธฐ](https://1-blue.github.io/posts/React-%EC%8A%A4%ED%81%AC%EB%A1%A4-%EB%B0%A9%ED%96%A5/)
3. [`React-Router-Dom`์˜ `replace`](https://1-blue.github.io/posts/React-Router-Dom/)
4. [`AWS-S3` - `presignedURL` ์‚ฌ์šฉ ๋ฐฉ๋ฒ•](https://1-blue.github.io/posts/AWS-S3-presignedURL/)
5. [`Node.js` + `TypeScript` ์„ธํŒ… ๋ฐฉ๋ฒ•](https://1-blue.github.io/posts/Setting-NodeJs/)
6. [`IntersectionObserver API`์™€ ๋ฌดํ•œ ์Šคํฌ๋กค๋ง](https://1-blue.github.io/posts/Intersection-Observer-API/)
7. [`Debouncing`๊ณผ ์‚ฌ์šฉ ์˜ˆ์‹œ](https://1-blue.github.io/posts/%EB%94%94%EB%B0%94%EC%9A%B4%EC%8B%B1-%EC%93%B0%EB%A1%9C%ED%8B%80%EB%A7%81-%EB%A9%94%EB%AA%A8%EC%9D%B4%EC%A0%9C%EC%9D%B4%EC%85%98/#%EF%B8%8F-%EB%94%94%EB%B0%94%EC%9A%B4%EC%8B%B1--debouncing-)
8. [`prisma` ์‚ฌ์šฉ๋ฒ• ์ •๋ฆฌ](https://1-blue.github.io/posts/prisma/)
9. [`Redux-ToolKit` + `TypeScript` + `React` ์‚ฌ์šฉ ๋ฐฉ๋ฒ•](https://1-blue.github.io/posts/Redux-Toolkit/)
10. [`blequotes` ๋งˆ๋ฌด๋ฆฌ ํฌ์ŠคํŠธ](https://1-blue.github.io/posts/bleqoutes-%EB%A7%88%EB%AC%B4%EB%A6%AC/)

# ๐Ÿ“ธ ์‹คํ–‰ ์˜์ƒ
## 0๏ธโƒฃ ๋ฐ˜์‘ํ˜• ๋ ˆ์ด์•„์›ƒ 1
๋ฐ˜์‘ํ˜• ๋ ˆ์ด์•„์›ƒ 1

## 1๏ธโƒฃ ๋ฐ˜์‘ํ˜• ๋ ˆ์ด์•„์›ƒ 2
๋ฐ˜์‘ํ˜• ๋ ˆ์ด์•„์›ƒ 2

## 2๏ธโƒฃ ๋ฌดํ•œ ์Šคํฌ๋กค๋ง
๋ฌดํ•œ ์Šคํฌ๋กค๋ง

## 3๏ธโƒฃ ์Šค์ผˆ๋ ˆํ†ค UI
์Šค์ผˆ๋ ˆํ†ค UI

## 4๏ธโƒฃ ๊ฒ€์ƒ‰
๊ฒ€์ƒ‰

## 5๏ธโƒฃ ๊ฒŒ์‹œ๊ธ€ ์ƒ์„ฑ
๊ฒŒ์‹œ๊ธ€ ์ƒ์„ฑ


# ๐Ÿค API
```ts
type ApiType = "axios" | "prisma" | "unknown";

// ์„ฑ๊ณต ์‹œ response ํƒ€์ž…
type ApiResponse = {
meta: { ok: boolean; type?: ApiType };
data: { message: string } & T;
};

// 500 ์‹คํŒจ ์‹œ response ํƒ€์ž…
type ApiErrorResponse = ApiResponse<{}>;
```

## 0๏ธโƒฃ Movie
```ts
// ์š”์ฒญํ•  ์˜ํ™” ์นดํ…Œ๊ณ ๋ฆฌ ( ์ธ๊ธฐ, ํ˜„์žฌ ์ƒ์˜ ๋“ฑ )
type MovieCategory = "popular" | "top_rated" | "now_playing";

// ์˜ํ™” ์–ธ์–ด
type MovieLanguage = "ko-kr" | "en-us";

// ์˜ํ™” ํƒ€์ž…
type Movie = {
adult: boolean;
backdrop_path: string;
genre_ids: number[];
id: number;
original_language: string;
original_title: string;
overview: string;
popularity: number;
poster_path: string;
release_date: string;
title: string;
video: false;
vote_average: number;
vote_count: number;
};

// ์˜ํ™” ์ƒ์„ธ ํƒ€์ž…
type DetailMovie = {
adult: boolean;
backdrop_path: string;
belongs_to_collection: {
id: number;
name: string;
poster_path: string;
backdrop_path: string;
};
budget: number;
genres: {
id: number;
name: string;
}[];
homepage: string;
id: number;
imdb_id: string;
original_language: string;
original_title: string;
overview: string;
popularity: number;
poster_path: string;
production_companies: {
id: number;
logo_path: string;
name: string;
origin_country: string;
}[];
production_countries: {
iso_3166_1: string;
name: string;
}[];
release_date: string;
revenue: number;
runtime: number;
spoken_languages: {
english_name: string;
iso_639_1: string;
name: string;
}[];
status: string;
tagline: string;
title: string;
video: boolean;
vote_average: number;
vote_count: number;
};
```

### 1. ์ธ๊ธฐ / ์ตœ์‹  / ๊พธ์ค€ํ•œ ์ธ๊ธฐ ์˜ํ™”๋“ค ์š”์ฒญ
`GET` - `api/movie`

+ `Status Code`
1. `200`: ์„ฑ๊ณต
2. `500`: ์„œ๋ฒ„ ๋ฌธ์ œ ์‹คํŒจ

+ `request`
```ts
type FetchMoviesRequest = {
category: MovieCategory;
language?: MovieLanguage;
};
```

+ `response`
```ts
type FetchMoviesResponse = ApiResponse<{ movies: Movie[] }>
```

### 2. ์˜ํ™” ๊ฒ€์ƒ‰ ์š”์ฒญ
`GET` - `api/movie/search`

+ `Status Code`
1. `200`: ์„ฑ๊ณต
2. `500`: ์„œ๋ฒ„ ๋ฌธ์ œ ์‹คํŒจ

+ `request`
```ts
type SearchMoviesRequest = {
title: string;
language?: MovieLanguage;
};
```

+ `response`
```ts
type SearchMoviesResponse = ApiResponse<{ movies: Movie[] }>
```

### 3. ์˜ํ™” ์ถ”์ฒœ ๊ฒ€์ƒ‰์–ด๋“ค ์š”์ฒญ
`GET` - `api/movie/suggested`

+ `Status Code`
1. `200`: ์„ฑ๊ณต
2. `500`: ์„œ๋ฒ„ ๋ฌธ์ œ ์‹คํŒจ

+ `request`
```ts
type SuggestMoviesRequest = {
keyword: string;
language?: MovieLanguage;
};
```

+ `response`
```ts
type SearchMoviesResponse = ApiResponse<{ titles: string[] }>
```

### 4. ์œ ์‚ฌ ์˜ํ™”๋“ค ์š”์ฒญ
`GET` - `api/movie/similar`

+ `Status Code`
1. `200`: ์„ฑ๊ณต
2. `500`: ์„œ๋ฒ„ ๋ฌธ์ œ ์‹คํŒจ

+ `request`
```ts
type SimilarMoviesRequest = {
movieIdx: string; // "Movie DB"์—์„œ ์ œ๊ณตํ•œ ์‹๋ณ„์ž
language?: MovieLanguage;
};
```

+ `response`
```ts
type SimilarMoviesResponse = ApiResponse<{ movies: Movie[] }>
```

### 5. ํŠน์ • ์˜ํ™” ์ƒ์„ธ ์ •๋ณด ์š”์ฒญ
`GET` - `api/movie/detail`

+ `Status Code`
1. `200`: ์„ฑ๊ณต
2. `500`: ์„œ๋ฒ„ ๋ฌธ์ œ ์‹คํŒจ

+ `request`
```ts
type DetailMovieRequest = {
movieIdx: string; // "Movie DB"์—์„œ ์ œ๊ณตํ•œ ์‹๋ณ„์ž
language?: MovieLanguage;
};
```

+ `response`
```ts
type DetailMovieResponse = ApiResponse<{ movie: DetailMovie }>
```

## 1๏ธโƒฃ Drama
```ts
// ์š”์ฒญํ•  ๋“œ๋ผ๋งˆ ์นดํ…Œ๊ณ ๋ฆฌ ( ์ธ๊ธฐ, ํ˜„์žฌ ์ƒ์˜ ๋“ฑ )
type DramaCategory = "popular" | "top_rated" | "on_the_air";

// ๋“œ๋ผ๋งˆ ์–ธ์–ด
type DramaLanguage = "ko-kr" | "en-us";

// ๋“œ๋ผ๋งˆ ํƒ€์ž…
type Drama = {
backdrop_path: string;
first_air_date: string;
genre_ids: number[];
id: number;
name: string;
origin_country: string[];
original_language: string;
original_name: string;
overview: string;
popularity: number;
poster_path: string;
vote_average: number;
vote_count: number;
};

// ๋“œ๋ผ๋งˆ ์ƒ์„ธ ํƒ€์ž…
type DetailDrama = {
adult: boolean;
backdrop_path: string;
created_by: {
id: number;
credit_id: string;
name: string;
gender: number;
profile_path: string | null;
}[];
episode_run_time: number[];
first_air_date: string;
genres: {
id: number;
name: string;
}[];
homepage: string;
id: number;
in_production: boolean;
languages: string[];
last_air_date: string;
last_episode_to_air: {
air_date: string;
episode_number: number;
id: number;
name: string;
overview: string;
production_code: string;
runtime: number;
season_number: number;
show_id: number;
still_path: string;
vote_average: number;
vote_count: number;
};
name: string;
next_episode_to_air: null | string;
networks: {
id: number;
name: string;
logo_path: string;
origin_country: string;
}[];
number_of_episodes: number;
number_of_seasons: number;
origin_country: string[];
original_language: string;
original_name: string;
overview: string;
popularity: number;
poster_path: string;
production_companies: {
id: number;
logo_path: null | string;
name: string;
origin_country: string;
}[];
production_countries: {
iso_3166_1: string;
name: string;
}[];
seasons: {
air_date: string;
episode_count: number;
id: number;
name: string;
overview: string;
poster_path: string;
season_number: number;
}[];
spoken_languages: {
english_name: string;
iso_639_1: string;
name: string;
}[];
status: string;
tagline: string;
type: string;
vote_average: number;
vote_count: number;
}
```

### 1. ์ธ๊ธฐ / ์ตœ์‹  / ๊พธ์ค€ํ•œ ์ธ๊ธฐ ๋“œ๋ผ๋งˆ๋“ค ์š”์ฒญ
`GET` - `api/drama`

+ `Status Code`
1. `200`: ์„ฑ๊ณต
2. `500`: ์„œ๋ฒ„ ๋ฌธ์ œ ์‹คํŒจ

+ `request`
```ts
type FetchDramasRequest = {
category: DramaCategory;
language?: DramaLanguage;
};
```

+ `response`
```ts
type DetailBookResponse = ApiResponse<{ dramas: Drama[] }>
```

### 2. ๋“œ๋ผ๋งˆ ๊ฒ€์ƒ‰ ์š”์ฒญ
`GET` - `api/drama/search`

+ `Status Code`
1. `200`: ์„ฑ๊ณต
2. `500`: ์„œ๋ฒ„ ๋ฌธ์ œ ์‹คํŒจ

+ `request`
```ts
type SearchDramasRequest = {
title: string;
language?: DramaLanguage;
};
```

+ `response`
```ts
type SearchDramasResponse = ApiResponse<{ dramas: Drama[] }>
```

### 3. ๋“œ๋ผ๋งˆ ์ถ”์ฒœ ๊ฒ€์ƒ‰์–ด๋“ค ์š”์ฒญ
`GET` - `api/drama/suggested`

+ `Status Code`
1. `200`: ์„ฑ๊ณต
2. `500`: ์„œ๋ฒ„ ๋ฌธ์ œ ์‹คํŒจ

+ `request`
```ts
type SuggestDramasRequest = {
keyword: string;
language?: DramaLanguage;
};
```

+ `response`
```ts
type SuggestDramasResponse = ApiResponse<{ titles: string[] }>
```

### 4. ์œ ์‚ฌ ๋“œ๋ผ๋งˆ๋“ค ์š”์ฒญ
`GET` - `api/drama/similar`

+ `Status Code`
1. `200`: ์„ฑ๊ณต
2. `500`: ์„œ๋ฒ„ ๋ฌธ์ œ ์‹คํŒจ

+ `request`
```ts
type SimilarDramasRequest = {
dramaIdx: string; // "Movie DB"์—์„œ ์ œ๊ณตํ•œ ์‹๋ณ„์ž
language?: DramaLanguage;
};
```

+ `response`
```ts
type SimilarDramasResponse = ApiResponse<{ dramas: Drama[] }>
```

### 5. ํŠน์ • ๋“œ๋ผ๋งˆ ์ƒ์… ์ •๋ณด ์š”์ฒญ
`GET` - `api/drama/detail`

+ `Status Code`
1. `200`: ์„ฑ๊ณต
2. `500`: ์„œ๋ฒ„ ๋ฌธ์ œ ์‹คํŒจ

+ `request`
```ts
type DetailDramaRequest = {
dramaIdx: string; // "Movie DB"์—์„œ ์ œ๊ณตํ•œ ์‹๋ณ„์ž
language?: DramaLanguage;
};
```

+ `response`
```ts
type DetailDramaResponse = ApiResponse<{ drama: DetailDrama }>
```

## 2๏ธโƒฃ Book
```ts
// ๋„์„œ ํƒ€์ž…
type Book = {
authors: string[];
contents: string;
datetime: string;
isbn: string;
price: number;
publisher: string;
sale_price: number;
status: string;
thumbnail: string;
title: string;
translators: string[];
url: string;
};
```

### 1. ํŠน์ • ๋„์„œ ๊ฒ€์ƒ‰ ์š”์ฒญ
`GET` - `api/book/search`

+ `Status Code`
1. `200`: ์„ฑ๊ณต
2. `500`: ์„œ๋ฒ„ ๋ฌธ์ œ ์‹คํŒจ

+ `request`
```ts
type SearchBooksRequest = {
title: string;
};
```

+ `response`
```ts
type SearchBooksResponse = ApiResponse<{ books: Book[] }>
```

### 2. ๋„์„œ ์ถ”์ฒœ ๊ฒ€์ƒ‰์–ด๋“ค ์š”์ฒญ
`GET` - `api/book/suggested`

+ `Status Code`
1. `200`: ์„ฑ๊ณต
2. `500`: ์„œ๋ฒ„ ๋ฌธ์ œ ์‹คํŒจ

+ `request`
```ts
type SuggestedBooksRequest = {
keyword: string;
};
```

+ `response`
```ts
type SuggestedBooksResponse = ApiResponse<{ titles: string[] }>
```

### 3. ํŠน์ • ๋„์„œ์™€ ์œ ์‚ฌํ•œ ๋„์„œ๋“ค ์š”์ฒญ
`GET` - `api/book/similar`

( ์ €์ž ๊ธฐ์ค€์œผ๋กœ ์œ ์‚ฌ ๋„์„œ ๊ฒ€์ƒ‰ )

+ `Status Code`
1. `200`: ์„ฑ๊ณต
2. `500`: ์„œ๋ฒ„ ๋ฌธ์ œ ์‹คํŒจ

+ `request`
```ts
type SimilarBooksRequest = {
author: string;
};
```

+ `response`
```ts
type SimilarBooksResponse = ApiResponse<{ books: Book[] }>
```

### 4. ํŠน์ • ๋„์„œ์˜ ์ƒ์„ธ ์ •๋ณด ์š”์ฒญ
`GET` - `api/book/detail`

+ `Status Code`
1. `200`: ์„ฑ๊ณต
2. `500`: ์„œ๋ฒ„ ๋ฌธ์ œ ์‹คํŒจ

+ `request`
```ts
type DetailBookRequest = {
bookIdx: string; // "Movie DB"์—์„œ ์‚ฌ์šฉํ•˜๋Š” ์‹๋ณ„์ž
};
```

+ `response`
```ts
type DetailBookResponse = ApiResponse<{ book: Book }>
```

## 3๏ธโƒฃ Image
### 1. S3์— presignedURL ์š”์ฒญ
`GET` - `api/image`

+ `Status Code`
1. `201`: ์„ฑ๊ณต
2. `500`: ์„œ๋ฒ„ ๋ฌธ์ œ ์‹คํŒจ

+ `request`
```ts
type FetchPresignedURLRequest = {
name: string;
};
```

+ `response`
```ts
type FetchPresignedURLResponse = ApiResponse<{ preSignedURL: string }>
```

## 4๏ธโƒฃ Post
```ts
export const PostCategory: {
MOVIE: 'MOVIE',
DRAMA: 'DRAMA',
BOOK: 'BOOK'
};

// ๊ฒŒ์‹œ๊ธ€ ์นดํ…Œ๊ณ ๋ฆฌ ( ์˜ํ™”, ๋“œ๋ผ๋งˆ, ๋„์„œ )
export type PostCategory = (typeof PostCategory)[keyof typeof PostCategory]

// ๊ฒŒ์‹œ๊ธ€๋“ค ์ •๋ ฌ ๊ธฐ์ค€
type PostSortBy = "popular" | "latest";

// ๊ฒŒ์‹œ๊ธ€
type Post = {
id: number
idx: string
title: string
category: PostCategory
speech: string
like: number
hate: number
updatedAt: Date
time: string | null
episode: number | null
page: number | null
thumbnail: string
}
```

+ `take`: ์š”์ฒญํ•œ ๊ฐœ์ˆ˜
+ `lastId`: ์ตœ๊ทผ์— ์š”์ฒญํ•œ ๋งˆ์ง€๋ง‰ ๊ฒŒ์‹œ๊ธ€์˜ ์‹๋ณ„์ž ( ์ด ๊ฐ’์„ ๊ธฐ์ค€์œผ๋กœ ์‘๋‹ตํ•  ๊ฒŒ์‹œ๊ธ€์„ ๊ฒฐ์ • )

### 1. ๊ฒŒ์‹œ๊ธ€ ์ƒ์„ฑ ์š”์ฒญ
`POST` - `api/post`

+ `Status Code`
1. `201`: ๊ฒŒ์‹œ๊ธ€ ์ƒ์„ฑ ์„ฑ๊ณต
2. `500`: ์„œ๋ฒ„ ๋ฌธ์ œ ์‹คํŒจ

+ `request`
```ts
type CreatePostRequest = {
idx: string;
title: string;
category: PostCategory;
speech: string;
thumbnail: string;

// ์˜ํ™” / ๋“œ๋ผ๋งˆ ์šฉ
time?: string;

// ๋“œ๋ผ๋งˆ ์šฉ
episode?: number;

// ๋„์„œ ์šฉ
page?: number;
};
```

+ `response`
```ts
type CreatePostResponse = ApiResponse<{}>
```

### 2. ๊ฒŒ์‹œ๊ธ€๋“ค ๊ฐ€์ ธ์˜ค๊ธฐ ์š”์ฒญ
`GET` - `api/post`

+ `Status Code`
1. `200`: ๊ฒŒ์‹œ๊ธ€๋“ค ๊ฐ€์ ธ์˜ค๊ธฐ ์„ฑ๊ณต
2. `500`: ์„œ๋ฒ„ ๋ฌธ์ œ ์‹คํŒจ

+ `request`
```ts
type GetPostsRequest = {
category: "ALL" | PostCategory;
sortBy: PostSortBy;
take: number;
lastId: number;
};
```

+ `response`
```ts
type GetPostsResponse = ApiResponse<{
take: number; // ๋งˆ์ง€๋ง‰ ๊ฒŒ์‹œ๊ธ€์ธ์ง€ ํŒ๋‹จํ•˜๊ธฐ ์œ„ํ•ด ๋ณด๋ƒ„
category: "ALL" | PostCategory;
posts: Post[];
}>
```

### 3. ํŠน์ • ๊ฒŒ์‹œ๊ธ€ ์ข‹์•„์š”/์‹ซ์–ด์š” ์š”์ฒญ
`POST` - `api/post/like`

+ `Status Code`
1. `200`: ์ข‹์•„์š”/์‹ซ์–ด์š” ์„ฑ๊ณต
2. `404`: ์กด์žฌํ•˜์ง€ ์•Š์€ ๊ฒŒ์‹œ๊ธ€์— ์š”์ฒญํ•จ
3. `500`: ์„œ๋ฒ„ ๋ฌธ์ œ ์‹คํŒจ

+ `request`
```ts
type UpdateLikeOrHateRequest = {
id: number;
already: boolean; // ์ด๋ฏธ ์ข‹์•„์š”/์‹ซ์–ด์š”๋ฅผ ๋ˆŒ๋ €๋Š”์ง€ ์—ฌ๋ถ€
isLike: boolean; // ์ข‹์•„์š”๋ฅผ ๋ˆŒ๋ €๋Š”์ง€ / ์‹ซ์–ด์š”๋ฅผ ๋ˆŒ๋ €๋Š”์ง€
isDuplication: boolean; // ์ข‹์•„์š”๋ฅผ ๋ˆ„๋ฅธ ์ƒํƒœ์—์„œ ๋‹ค์‹œ ์ข‹์•„์š” / ์‹ซ์–ด์š”๋ฅผ ๋ˆ„๋ฅธ ์ƒํƒœ์—์„œ ๋‹ค์‹œ ์‹ซ์–ด์š”๋ฅผ ๋ˆŒ๋ €๋Š”์ง€
}
```

+ `response`
```ts
type GetPostsResponse = ApiResponse<{ resultPost?: Post }>
```

### 4. ํŠน์ • ๋Œ€์ƒ์˜ ๊ฒŒ์‹œ๊ธ€๋“ค ์š”์ฒญ
`GET` - `api/post/:idx`

+ `Status Code`
1. `200`: ์ข‹์•„์š”/์‹ซ์–ด์š” ์„ฑ๊ณต
2. `500`: ์„œ๋ฒ„ ๋ฌธ์ œ ์‹คํŒจ

+ `request`
```ts
type GetPostsOfTargetRequest = {
idx: string;
sortBy: PostSortBy;
take: number;
lastId: number;
}
```

+ `response`
```ts
type GetPostsOfTargetResponse = ApiResponse<{
take: number; // ๋งˆ์ง€๋ง‰ ๊ฒŒ์‹œ๊ธ€์ธ์ง€ ํŒ๋‹จํ•˜๊ธฐ ์œ„ํ•ด ๋ณด๋ƒ„
posts: Post[];
}>
```