feat: added category route

close: https://github.com/koreanbots/v2-testing/issues/16
This commit is contained in:
원더 2021-02-04 21:36:36 +09:00
parent 88c452fa26
commit 6bd13bb45f
3 changed files with 90 additions and 3 deletions

View File

@ -0,0 +1,63 @@
import { NextPage, NextPageContext } from 'next'
import dynamic from 'next/dynamic'
import { ParsedUrlQuery } from 'querystring'
import { get } from '@utils/Query'
import { BotList } from '@types'
import { botCategoryListArgumentSchema } from '@utils/Yup'
import NotFound from 'pages/404'
const Hero = dynamic(() => import('@components/Hero'))
const Advertisement = dynamic(() => import('@components/Advertisement'))
const SEO = dynamic(() => import('@components/SEO'))
const BotCard = dynamic(() => import('@components/BotCard'))
const Container = dynamic(() => import('@components/Container'))
const Paginator = dynamic(() => import('@components/Paginator'))
const Category: NextPage<CategoryProps> = ({ data, query }) => {
if(!data || data.data.length === 0 || data.totalPage < Number(query.page)) return <NotFound />
return <>
<Hero header={`${query.category} 카테고리 봇들`} description={`다양한 "${query.category}" 카테고리의 봇들을 만나보세요.`} />
<SEO title={`${query.category} 카테고리 봇들`} description={`다양한 ${query.category} 카테고리의 봇들을 만나보세요.`} />
<Container>
<Advertisement />
<div className='grid gap-4 2xl:grid-cols-4 md:grid-cols-2 mt-20'>
{
data.data.map(bot => <BotCard key={bot.id} bot={bot} /> )
}
</div>
<Paginator totalPage={data.totalPage} currentPage={data.currentPage} pathname={`/categories/${query.category}`} />
</Container>
</>
}
export const getServerSideProps = async (ctx: Context) => {
let data: BotList
if(!ctx.query.page) ctx.query.page = '1'
const validate = await botCategoryListArgumentSchema.validate(ctx.query).then(el => el).catch(() => null)
if(!validate || isNaN(Number(ctx.query.page))) data = null
else data = await get.list.category.load(JSON.stringify({ page: Number(ctx.query.page), category: ctx.query.category }))
return {
props: {
data,
query: ctx.query
}
}
}
interface CategoryProps {
data: BotList
query: URLQuery
}
interface Context extends NextPageContext {
query: URLQuery
}
interface URLQuery extends ParsedUrlQuery {
category: string
page: string
}
export default Category

View File

@ -259,6 +259,13 @@ export const get = {
(await Promise.all(ids.map(async (el: string) => await getUser(el, false)))).map(row => ({ ...row })) (await Promise.all(ids.map(async (el: string) => await getUser(el, false)))).map(row => ({ ...row }))
, { cacheMap: new TLRU({ maxStoreSize: 50, maxAgeMs: 60000 }) }), , { cacheMap: new TLRU({ maxStoreSize: 50, maxAgeMs: 60000 }) }),
list: { list: {
category: new DataLoader(
async (key: string[]) =>
(await Promise.all(key.map(async (k: string) => {
const json = JSON.parse(k)
return await getBotList('CATEGORY', json.page, json.category)
}))).map(row => ({ ...row }))
, { cacheMap: new TLRU({ maxStoreSize: 50, maxAgeMs: 3000000 }) }),
votes: new DataLoader( votes: new DataLoader(
async (pages: number[]) => async (pages: number[]) =>
(await Promise.all(pages.map(async (page: number) => await getBotList('VOTE', page)))).map(row => ({ ...row })) (await Promise.all(pages.map(async (page: number) => await getBotList('VOTE', page)))).map(row => ({ ...row }))

View File

@ -6,8 +6,8 @@ import { HTTPProtocol, ID, Prefix, Url, Vanity } from './Regex'
Yup.setLocale(YupKorean) Yup.setLocale(YupKorean)
export const botListArgumentSchema = Yup.object({ export const botListArgumentSchema: Yup.SchemaOf<botListArgument> = Yup.object({
type: Yup.string().oneOf(['VOTE', 'TRUSTED', 'NEW', 'PARTNERED', 'CATEGORY', 'SEARCH']).required(), type: Yup.mixed().oneOf(['VOTE', 'TRUSTED', 'NEW', 'PARTNERED', 'CATEGORY', 'SEARCH']).required(),
page: Yup.number().positive().integer().notRequired().default(1), page: Yup.number().positive().integer().notRequired().default(1),
query: Yup.string().notRequired() query: Yup.string().notRequired()
}) })
@ -33,16 +33,33 @@ interface ImageOptions {
type ext = 'webp' | 'png' | 'gif' type ext = 'webp' | 'png' | 'gif'
type ImageSize = '128' | '256' | '512' type ImageSize = '128' | '256' | '512'
export const PageCount = Yup.number().integer().positive() export const PageCount = Yup.number().integer().positive().required()
export const OauthCallbackSchema: Yup.SchemaOf<OauthCallback> = Yup.object({ export const OauthCallbackSchema: Yup.SchemaOf<OauthCallback> = Yup.object({
code: Yup.string().required() code: Yup.string().required()
}) })
export const botCategoryListArgumentSchema: Yup.SchemaOf<botCategoryListArgument> = Yup.object({
page: PageCount,
category: Yup.mixed().oneOf(categories).required()
})
interface botCategoryListArgument {
page: number
category: string
}
interface OauthCallback { interface OauthCallback {
code: string code: string
} }
export const SearchQuerySchema: Yup.SchemaOf<SearchQuery> = Yup.object({
q: Yup.string().min(2, '최소 2글자 이상 입력해주세요.').required()
})
interface SearchQuery {
q: string
}
export const AddBotSubmitSchema = Yup.object({ export const AddBotSubmitSchema = Yup.object({
agree: Yup.boolean().oneOf([true], '상단의 체크박스를 클릭해주세요.').required('상단의 체크박스를 클릭해주세요.'), agree: Yup.boolean().oneOf([true], '상단의 체크박스를 클릭해주세요.').required('상단의 체크박스를 클릭해주세요.'),
id: Yup.string().matches(ID, '올바른 봇 ID를 입력해주세요.').required('봇 ID는 필수 항목입니다.'), id: Yup.string().matches(ID, '올바른 봇 ID를 입력해주세요.').required('봇 ID는 필수 항목입니다.'),