import { Context } from 'elysia' import { getClientIp, getDatabaseConnection, hash, jsonResponse, verifyTurstileOrVerifyCode } from '../../../../lib/util' import { checkAuthorization } from '../../../../lib/auth' import { berryDashMarketplaceIcons } from '../../../../lib/tables' import { Buffer } from 'buffer' import sizeOf from 'image-size' import { Connection } from 'mysql2/typings/mysql/lib/Connection' type Body = { token: string | null verifyCode: string | null price: string name: string fileContent: string } function exitBecauseInvalid ( connection0: Connection, connection1: Connection, message: string ) { connection0.end() connection1.end() return jsonResponse( { success: false, message: message }, 400 ) } export const handler = async (context: Context) => { const dbInfo0 = getDatabaseConnection(0) const dbInfo1 = getDatabaseConnection(1) if (!dbInfo0 || !dbInfo1) return jsonResponse( { success: false, message: 'Failed to connect to database' }, 500 ) const { connection: connection0, db: db0 } = dbInfo0 const { connection: connection1, db: db1 } = dbInfo1 const ip = getClientIp(context) if (!ip) { connection0.end() connection1.end() return jsonResponse( { success: false, message: 'Failed to get required info' }, 400 ) } const authorizationToken = context.headers.authorization const authResult = await checkAuthorization( authorizationToken as string, db0, ip ) if (!authResult.valid) { connection0.end() connection1.end() return jsonResponse({ success: false, message: 'Unauthorized' }, 401) } const userId = authResult.id const body = context.body as Body const price = parseInt(body.price, 10) if (isNaN(price)) { connection0.end() connection1.end() return jsonResponse( { success: false, message: 'Failed to parse price' }, 400 ) } if (price < 10) { connection0.end() connection1.end() return exitBecauseInvalid( connection0, connection1, 'Price cannot be be under 10 coins' ) } if (!/^[a-zA-Z0-9 ]+$/.test(body.name) || body.name.length > 16) { connection0.end() connection1.end() return exitBecauseInvalid(connection0, connection1, 'Name is invalid') } const decoded = Buffer.from(body.fileContent, 'base64') if (!decoded) { connection0.end() connection1.end() return exitBecauseInvalid( connection0, connection1, 'Invalid image uploaded' ) } if (decoded.length > 1024 * 1024) { connection0.end() connection1.end() return exitBecauseInvalid( connection0, connection1, 'File size exceeds 1 MB limit' ) } const info = sizeOf(decoded) if (!info) { connection0.end() connection1.end() return exitBecauseInvalid( connection0, connection1, 'Invalid image uploaded' ) } if (info.type !== 'png') { connection0.end() connection1.end() return exitBecauseInvalid(connection0, connection1, 'Image must be a PNG') } if (info.width !== 128 || info.height !== 128) { connection0.end() connection1.end() return exitBecauseInvalid( connection0, connection1, 'Image has to be 128x128' ) } const time = Math.floor(Date.now() / 1000) if ( !(await verifyTurstileOrVerifyCode(body.token, body.verifyCode, ip, db0)) ) { connection0.end() connection1.end() return jsonResponse( { success: false, message: body.token != null ? 'Invalid captcha token' : 'Invalid verify code (codes can only be used once)' }, 400 ) } const hashResult = hash(atob(body.fileContent), 'sha512') const id = crypto.randomUUID() await db1.insert(berryDashMarketplaceIcons).values({ id, userId, data: body.fileContent, hash: hashResult, price, name: btoa(body.name), timestamp: time }) connection0.end() connection1.end() return jsonResponse({ success: true, message: 'Icon uploaded successfully! It will be reviewed soon.' }) }