diff --git a/pages/api/v1/bots/servers.ts b/pages/api/v1/bots/servers.ts new file mode 100644 index 0000000..6336bac --- /dev/null +++ b/pages/api/v1/bots/servers.ts @@ -0,0 +1,50 @@ +import { NextApiRequest} from 'next' +import rateLimit from 'express-rate-limit' + +import { get, update } from '@utils/Query' +import RequestHandler from '@utils/RequestHandler' +import ResponseWrapper from '@utils/ResponseWrapper' +import { BotStatUpdate, BotStatUpdateSchema } from '@utils/Yup' + +const limiter = rateLimit({ + windowMs: 60 * 1000, + max: 1, + statusCode: 429, + handler: (_req, res) => ResponseWrapper(res, { code: 429 }), + keyGenerator: (req) => req.headers.authorization, + skip: (req) => { + if(!req.headers.authorization) return true + else return false + } +}) + +const BotStats = RequestHandler() + .post(limiter) + .post(async (req: PostApiRequest, res) => { + const bot = await get.BotAuthorization(req.headers.token) + if(!bot) return ResponseWrapper(res, { code: 401 }) + const validated: BotStatUpdate = await BotStatUpdateSchema.validate(req.body, { abortEarly: false }) + .then(el => el) + .catch(e => { + ResponseWrapper(res, { code: 400, errors: e.errors }) + return null + }) + + if(!validated) return + const botInfo = await get.bot.load(bot) + if(!botInfo) return ResponseWrapper(res, { code: 404, message: '존재하지 않는 봇입니다.' }) + if(botInfo.id !== bot) return ResponseWrapper(res, { code: 403 }) + const d = await update.updateServer(botInfo.id, validated.servers) + if(d===1 || d===2) return ResponseWrapper(res, { code: 403, message: `서버 수를 ${[null, '1만', '100만'][d]} 이상으로 설정하실 수 없습니다. 문의해주세요.` }) + return ResponseWrapper(res, { code: 200, message: '성공적으로 업데이트 했습니다.'}) + }) + + +interface PostApiRequest extends NextApiRequest { + headers: { + token: string + } + body: BotStatUpdate +} + +export default BotStats \ No newline at end of file diff --git a/pages/api/v2/bots/[id].ts b/pages/api/v2/bots/[id]/index.ts similarity index 100% rename from pages/api/v2/bots/[id].ts rename to pages/api/v2/bots/[id]/index.ts diff --git a/pages/api/v2/bots/[id]/stats.ts b/pages/api/v2/bots/[id]/stats.ts new file mode 100644 index 0000000..a3efa97 --- /dev/null +++ b/pages/api/v2/bots/[id]/stats.ts @@ -0,0 +1,50 @@ +import { NextApiRequest} from 'next' +import rateLimit from 'express-rate-limit' + +import { get, update } from '@utils/Query' +import RequestHandler from '@utils/RequestHandler' +import ResponseWrapper from '@utils/ResponseWrapper' +import { BotStatUpdate, BotStatUpdateSchema } from '@utils/Yup' + +const limiter = rateLimit({ + windowMs: 3 * 60 * 1000, + max: 3, + statusCode: 429, + handler: (_req, res) => ResponseWrapper(res, { code: 429 }), + keyGenerator: (req) => req.headers.authorization, + skip: (req) => { + if(!req.headers.authorization) return true + else return false + } +}) + +const BotStats = RequestHandler().post(limiter) + .post(async (req: PostApiRequest, res) => { + const bot = await get.BotAuthorization(req.headers.authorization) + if(!bot) return ResponseWrapper(res, { code: 401 }) + if(!req.body) return ResponseWrapper(res, { code: 400 }) + const validated: BotStatUpdate = await BotStatUpdateSchema.validate(req.body, { abortEarly: false }) + .then(el => el) + .catch(e => { + ResponseWrapper(res, { code: 400, errors: e.errors }) + return null + }) + + if(!validated) return + const botInfo = await get.bot.load(req.query.id) + if(!botInfo) return ResponseWrapper(res, { code: 404, message: '존재하지 않는 봇입니다.' }) + if(botInfo.id !== bot) return ResponseWrapper(res, { code: 403 }) + const d = await update.updateServer(botInfo.id, validated.servers) + if(d===1 || d===2) return ResponseWrapper(res, { code: 403, message: `서버 수를 ${[null, '1만', '100만'][d]} 이상으로 설정하실 수 없습니다. 문의해주세요.` }) + return ResponseWrapper(res, { code: 200, message: '성공적으로 업데이트 했습니다.'}) + }) + + +interface PostApiRequest extends NextApiRequest { + query: { + id: string + } + body: BotStatUpdate +} + +export default BotStats \ No newline at end of file diff --git a/utils/Query.ts b/utils/Query.ts index 53fcff6..6813c56 100644 --- a/utils/Query.ts +++ b/utils/Query.ts @@ -225,6 +225,18 @@ async function getBotSpec(id: string, userID: string) { return serialize(res[0]) } +/** + * @returns 1 - Limit of 100k servers + * @returns 2 - Limit of 10M servers + */ +async function updateServer(id: string, servers: number) { + const bot = await get.bot.load(id) + if(bot.servers < 10000 && servers >= 10000) return 1 + if(bot.servers < 1000000 && servers >= 1000000) return 2 + await knex('bots').update({ servers }).where({ id }) + return +} + async function updateBotApplication(id: string, value: { webhook: string }) { const bot = await knex('bots').update({ webhook: value.webhook }).where({ id }) if(bot !== 1) return false @@ -271,6 +283,13 @@ async function Authorization(token: string):Promise { else return user[0].id } +async function BotAuthorization(token: string):Promise { + const tokenInfo = verify(token ?? '') + const bot = await knex('bots').select(['id']).where({ id: tokenInfo?.id ?? '', token: token ?? '' }) + if(bot.length === 0) return false + else return bot[0].id +} + async function addRequest(ip: string, map: TLRU) { if(!map.has(ip)) map.set(ip, 0) map.set(ip, map.get(ip) + 1) @@ -346,13 +365,15 @@ export const get = { (await Promise.all(urls.map(async (url: string) => await getImage(url)))) , { cacheMap: new TLRU({ maxStoreSize: 500, maxAgeMs: 3600000 }) }), }, - Authorization + Authorization, + BotAuthorization } export const update = { assignToken, updateBotApplication, - resetBotToken + resetBotToken, + updateServer } export const put = { diff --git a/utils/Yup.ts b/utils/Yup.ts index 6caba65..964f591 100644 --- a/utils/Yup.ts +++ b/utils/Yup.ts @@ -184,6 +184,17 @@ export interface AddBotSubmit { _csrf: string } +export const BotStatUpdateSchema: Yup.SchemaOf = Yup.object({ + servers: Yup.number() + .positive('서버 수는 양수여야합니다.') + .integer('서버 수는 정수여야합니다.') + .required() +}) + +export interface BotStatUpdate { + servers: number +} + export const ManageBotSchema = Yup.object({ prefix: Yup.string() .matches(Prefix, '접두사는 띄어쓰기로 시작할 수 없습니다.')