Files
api/src/routes/berrydash/icon-marketplace/upload/post.ts
2026-02-08 01:26:43 -07:00

187 lines
4.1 KiB
TypeScript

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.'
})
}