-
Notifications
You must be signed in to change notification settings - Fork 7
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
* feat: books contract 작성 * feat: 400, 500 에러 스키마 추가 * feat: 기부자 수정 api contract 추가 * refactor: 기부자 수정 api 404 에러 스키마 추가 * refactor: zod.extend 활용해 querySchema 중복 제거 * refactor: book status, z.enum 적용
- Loading branch information
1 parent
dff0587
commit 32547b9
Showing
3 changed files
with
331 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,150 @@ | ||
import { initContract } from "@ts-rest/core"; | ||
import { | ||
searchAllBooksQuerySchema, | ||
searchAllBooksResponseSchema, | ||
searchBookByIdResponseSchema, | ||
searchBookInfoCreateQuerySchema, | ||
searchBookInfoCreateResponseSchema, | ||
createBookBodySchema, | ||
createBookResponseSchema, | ||
categoryNotFoundSchema, | ||
formatErrorSchema, | ||
insertionFailureSchema, | ||
isbnNotFoundSchema, | ||
naverBookNotFoundSchema, | ||
updateBookBodySchema, | ||
updateBookResponseSchema, | ||
unknownPatchErrorSchema, | ||
nonDataErrorSchema, | ||
searchBookInfosQuerySchema, | ||
searchBookInfosResponseSchema, | ||
searchBookInfosSortedQuerySchema, | ||
searchBookInfosSortedResponseSchema, | ||
searchBookInfoByIdQuerySchema, | ||
searchBookInfoByIdResponseSchema, | ||
updateDonatorBodySchema, | ||
updateDonatorResponseSchema | ||
} from "./schema"; | ||
import { badRequestSchema, bookInfoNotFoundSchema, bookNotFoundSchema, serverErrorSchema } from "../shared"; | ||
|
||
const c = initContract(); | ||
|
||
export const booksContract = c.router( | ||
{ | ||
searchAllBookInfos: { | ||
method: 'GET', | ||
path: '/info/search', | ||
description: '책 정보(book_info)를 검색하여 가져온다.', | ||
query: searchBookInfosQuerySchema, | ||
responses: { | ||
200: searchBookInfosResponseSchema, | ||
400: badRequestSchema, | ||
500: serverErrorSchema, | ||
}, | ||
}, | ||
searchBookInfosByTag: { | ||
method: 'GET', | ||
path: '/info/tag', | ||
description: '똑같은 내용의 태그가 달린 책의 정보를 검색하여 가져온다.', | ||
query: searchBookInfosQuerySchema, | ||
responses: { | ||
200: searchBookInfosResponseSchema, | ||
400: badRequestSchema, | ||
500: serverErrorSchema, | ||
}, | ||
}, | ||
searchBookInfosSorted: { | ||
method: 'GET', | ||
path: '/info/sorted', | ||
description: '책 정보를 기준에 따라 정렬한다. 정렬기준이 popular일 경우 당일으로부터 42일간 인기순으로 한다.', | ||
query: searchBookInfosSortedQuerySchema, | ||
responses: { | ||
200: searchBookInfosSortedResponseSchema, | ||
400: badRequestSchema, | ||
500: serverErrorSchema, | ||
}, | ||
}, | ||
searchBookInfoById: { | ||
method: 'GET', | ||
path: '/info/:id', | ||
description: 'book_info테이블의 ID기준으로 책 한 종류의 정보를 가져온다.', | ||
query: searchBookInfoByIdQuerySchema, | ||
responses: { | ||
200: searchBookInfoByIdResponseSchema, | ||
404: bookInfoNotFoundSchema, | ||
500: serverErrorSchema | ||
} | ||
}, | ||
searchAllBooks: { | ||
method: 'GET', | ||
path: '/search', | ||
description: '개별 책 정보(book)를 검색하여 가져온다. 책이 대출할 수 있는지 확인 할 수 있음', | ||
query: searchAllBooksQuerySchema, | ||
responses: { | ||
200: searchAllBooksResponseSchema, | ||
400: badRequestSchema, | ||
500: serverErrorSchema, | ||
}, | ||
}, | ||
searchBookInfoForCreate: { | ||
method: 'GET', | ||
path: '/create', | ||
description: '책 생성을 위해 국립중앙도서관에서 ISBN으로 검색한 뒤에 책정보를 반환', | ||
query: searchBookInfoCreateQuerySchema, | ||
responses: { | ||
200: searchBookInfoCreateResponseSchema, | ||
303: isbnNotFoundSchema, | ||
310: naverBookNotFoundSchema, | ||
500: serverErrorSchema, | ||
} | ||
}, | ||
searchBookById: { | ||
method: 'GET', | ||
path: '/:bookId', | ||
description: 'book테이블의 ID기준으로 책 한 종류의 정보를 가져온다.', | ||
responses: { | ||
200: searchBookByIdResponseSchema, | ||
404: bookNotFoundSchema, | ||
500: serverErrorSchema, | ||
} | ||
}, | ||
createBook: { | ||
method: 'POST', | ||
path: '/create', | ||
description: '책 정보를 생성한다. bookInfo가 있으면 book에만 insert한다.', | ||
body: createBookBodySchema, | ||
responses: { | ||
200: createBookResponseSchema, | ||
308: insertionFailureSchema, | ||
309: categoryNotFoundSchema, | ||
311: formatErrorSchema, | ||
500: serverErrorSchema, | ||
}, | ||
}, | ||
updateBook: { | ||
method: 'PATCH', | ||
path: '/update', | ||
description: '책 정보를 수정합니다. book_info table or book table', | ||
body: updateBookBodySchema, | ||
responses: { | ||
204: updateBookResponseSchema, | ||
312: unknownPatchErrorSchema, | ||
313: nonDataErrorSchema, | ||
311: formatErrorSchema, | ||
500: serverErrorSchema, | ||
}, | ||
}, | ||
updateDonator: { | ||
method: 'PATCH', | ||
path: '/donator', | ||
description: '기부자 정보를 수정합니다.', | ||
body: updateDonatorBodySchema, | ||
responses: { | ||
204: updateDonatorResponseSchema, | ||
404: bookNotFoundSchema, | ||
500: serverErrorSchema, | ||
}, | ||
}, | ||
}, | ||
{ pathPrefix: '/books' }, | ||
) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,179 @@ | ||
import { metaSchema, positiveInt, mkErrorMessageSchema, statusSchema } from "../shared"; | ||
import { z } from "../zodWithOpenapi"; | ||
|
||
const commonQuerySchema = z.object({ | ||
query: z.string().optional(), | ||
page: positiveInt.default(0), | ||
limit: positiveInt.default(10), | ||
}); | ||
|
||
export const searchBookInfosQuerySchema = commonQuerySchema.extend({ | ||
sort: z.string(), | ||
category: z.string(), | ||
}); | ||
|
||
export const searchBookInfosSortedQuerySchema = z.object({ | ||
sort: z.string(), | ||
limit: positiveInt.default(10), | ||
}); | ||
|
||
export const searchBookInfoByIdQuerySchema = z.object({ | ||
id: positiveInt, | ||
}); | ||
|
||
export const searchAllBooksQuerySchema = commonQuerySchema; | ||
|
||
export const searchBookInfoCreateQuerySchema = z.object({ | ||
isbnQuery: z.string(), | ||
}); | ||
|
||
export const createBookBodySchema = z.object({ | ||
title: z.string(), | ||
isbn: z.string(), | ||
author: z.string(), | ||
publisher: z.string(), | ||
image: z.string(), | ||
categoryId: z.string(), | ||
pubdate: z.string(), | ||
donator: z.string(), | ||
}); | ||
|
||
export const updateBookBodySchema = z.object({ | ||
bookInfoId: positiveInt, | ||
categoryId: positiveInt, | ||
title: z.string(), | ||
author: z.string(), | ||
publisher: z.string(), | ||
publishedAt: z.string(), | ||
image: z.string(), | ||
bookId: positiveInt, | ||
callSign: z.string(), | ||
status: statusSchema, | ||
}); | ||
|
||
export const updateDonatorBodySchema = z.object({ | ||
bookId: positiveInt, | ||
nickname: z.string(), | ||
}); | ||
|
||
export const bookInfoSchema = z.object({ | ||
id: positiveInt, | ||
title: z.string(), | ||
author: z.string(), | ||
publisher: z.string(), | ||
isbn: z.string(), | ||
image: z.string(), | ||
category: z.string(), | ||
publishedAt: z.string(), | ||
createdAt: z.string(), | ||
updatedAt: z.string(), | ||
lendingCnt: positiveInt, | ||
}); | ||
|
||
export const searchBookInfosResponseSchema = z.object({ | ||
items: z.array( | ||
bookInfoSchema, | ||
), | ||
categories: z.array( | ||
z.object({ | ||
name: z.string(), | ||
count: positiveInt, | ||
}), | ||
), | ||
meta: metaSchema, | ||
}); | ||
|
||
export const searchBookInfosSortedResponseSchema = z.object({ | ||
items: z.array( | ||
bookInfoSchema, | ||
) | ||
}); | ||
|
||
export const searchBookInfoByIdResponseSchema = z.object({ | ||
bookInfoSchema, | ||
books: z.array( | ||
z.object({ | ||
id: positiveInt, | ||
callSign: z.string(), | ||
donator: z.string(), | ||
status: statusSchema, | ||
dueDate: z.string(), | ||
isLendable: positiveInt, | ||
isReserved: positiveInt, | ||
}), | ||
), | ||
}); | ||
|
||
export const searchAllBooksResponseSchema = z.object({ | ||
items: z.array( | ||
z.object({ | ||
bookId: positiveInt.openapi({ example: 1 }), | ||
bookInfoId: positiveInt.openapi({ example: 1 }), | ||
title: z.string().openapi({ example: '모두의 데이터 과학 with 파이썬' }), | ||
author: z.string().openapi({ example: '드미트리 지노비에프' }), | ||
donator: z.string().openapi({ example: 'mingkang' }), | ||
publisher: z.string().openapi({ example: '길벗' }), | ||
publishedAt: z.string().openapi({ example: '20170714' }), | ||
isbn: z.string().openapi({ example: '9791160502152' }), | ||
image: z.string().openapi({ example: 'https://image.kyobobook.co.kr/images/book/xlarge/152/x9791160502152.jpg' }), | ||
status: statusSchema.openapi({ example: 3 }), | ||
categoryId: positiveInt.openapi({ example: 8 }), | ||
callSign: z.string().openapi({ example: 'K23.17.v1.c1' }), | ||
category: z.string().openapi({ example: '데이터 분석/AI/ML' }), | ||
isLendable: positiveInt.openapi({ example: 0 }), | ||
}) | ||
), | ||
meta: metaSchema, | ||
}); | ||
|
||
export const searchBookInfoCreateResponseSchema = z.object({ | ||
bookInfo: z.object({ | ||
title: z.string().openapi({ example: '작별인사' }), | ||
image: z.string().openapi({ example: 'http://image.kyobobook.co.kr/images/book/xlarge/225/x9791191114225.jpg' }), | ||
author: z.string().openapi({ example: '지은이: 김영하' }), | ||
category: z.string().openapi({ example: '8' }), | ||
isbn: z.string().openapi({ example: '9791191114225' }), | ||
publisher: z.string().openapi({ example: '복복서가' }), | ||
pubdate: z.string().openapi({ example: '20220502' }), | ||
}), | ||
}) | ||
|
||
export const searchBookByIdResponseSchema = z.object({ | ||
id: positiveInt.openapi({ example: 3 }), | ||
bookId: positiveInt.openapi({ example: 3 }), | ||
bookInfoId: positiveInt.openapi({ example: 2}), | ||
title: z.string().openapi({ example: 'TCP IP 윈도우 소켓 프로그래밍(IT Cookbook 한빛 교재 시리즈 124)' }), | ||
author: z.string().openapi({ example: '김선우' }), | ||
donator: z.string().openapi({ example: 'mingkang' }), | ||
publisher: z.string().openapi({ example: '한빛아카데미' }), | ||
publishedAt: z.string().openapi({ example: '20130730' }), | ||
isbn: z.string().openapi({ example: '9788998756444' }), | ||
image: z.string().openapi({ example: 'https://image.kyobobook.co.kr/images/book/xlarge/444/x9788998756444.jpg' }), | ||
status: statusSchema.openapi({ example: 0 }), | ||
categoryId: positiveInt.openapi({ example: 2}), | ||
callsign: z.string().openapi({ example: 'C5.13.v1.c2' }), | ||
category: z.string().openapi({ example: '네트워크' }), | ||
isLendable: positiveInt.openapi({ example: 1 }), | ||
}); | ||
|
||
export const updateBookResponseSchema = z.literal('책 정보가 수정되었습니다.'); | ||
|
||
export const updateDonatorResponseSchema = z.literal('기부자 정보가 수정되었습니다.'); | ||
|
||
export const createBookResponseSchema = z.object({ | ||
callSign: z.string().openapi({ example: 'K23.17.v1.c1' }), | ||
}); | ||
|
||
export const isbnNotFoundSchema = mkErrorMessageSchema('ISBN_NOT_FOUND').describe('국립중앙도서관 API에서 ISBN 검색이 실패하였습니다.'); | ||
|
||
export const naverBookNotFoundSchema = mkErrorMessageSchema('NAVER_BOOK_NOT_FOUND').describe('네이버 책검색 API에서 ISBN 검색이 실패'); | ||
|
||
export const insertionFailureSchema = mkErrorMessageSchema('INSERT_FAILURE').describe('예상치 못한 에러로 책 정보 insert에 실패함.'); | ||
|
||
export const categoryNotFoundSchema = mkErrorMessageSchema('CATEGORY_NOT_FOUND').describe('보내준 카테고리 ID에 해당하는 callsign을 찾을 수 없음'); | ||
|
||
export const formatErrorSchema = mkErrorMessageSchema('FORMAT_ERROR').describe('입력한 pubdate가 알맞은 형식이 아님. 기대하는 형식 "20220807"'); | ||
|
||
export const unknownPatchErrorSchema = mkErrorMessageSchema('PATCH_ERROR').describe('예상치 못한 에러로 patch에 실패.'); | ||
|
||
export const nonDataErrorSchema = mkErrorMessageSchema('NO_DATA_ERROR').describe('DATA가 적어도 한 개는 필요.'); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters