Make icon marketplace upload endpoint

This commit is contained in:
2026-01-24 00:16:16 -07:00
parent bfd5d460c9
commit 0ae6a37bf6
5 changed files with 226 additions and 5 deletions

View File

@@ -0,0 +1,191 @@
import { Context } from 'elysia'
import {
getClientIp,
getDatabaseConnection,
hash,
jsonResponse
} from '../../../../lib/util'
import { checkAuthorization } from '../../../../lib/bd/auth'
import { berryDashMarketplaceIcons, verifyCodes } from '../../../../lib/tables'
import { and, desc, eq, sql } from 'drizzle-orm'
import { Buffer } from 'buffer'
import sizeOf from 'image-size'
import { Connection } from 'mysql2/typings/mysql/lib/Connection'
type Body = {
verifyCode: string
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 async function handler (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) ?? '127.0.0.1'
if (!ip) {
connection0.end()
return jsonResponse(
{
success: false,
message: 'Failed to get required info'
},
400
)
}
const authorizationToken = context.headers.authorization
const authResult = await checkAuthorization(
authorizationToken as string,
db1,
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
if (!body.name || !body.price || !body.fileContent) {
connection0.end()
connection1.end()
return jsonResponse(
{
success: false,
message: 'Name, price and fileContent must be in POST data'
},
400
)
}
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)
return exitBecauseInvalid(
connection0,
connection1,
'Price cannot be be under 10 coins'
)
if (!/^[a-zA-Z0-9 ]+$/.test(btoa(body.name)))
return exitBecauseInvalid(connection0, connection1, 'Name is invalid')
const decoded = Buffer.from(body.fileContent, 'base64')
if (!decoded)
return exitBecauseInvalid(
connection0,
connection1,
'Invalid image uploaded'
)
if (decoded.length > 1024 * 1024)
return exitBecauseInvalid(
connection0,
connection1,
'File size exceeds 1 MB limit'
)
const info = sizeOf(decoded)
if (!info)
return exitBecauseInvalid(
connection0,
connection1,
'Invalid image uploaded'
)
if (info.type !== 'png')
return exitBecauseInvalid(connection0, connection1, 'Image must be a PNG')
if (info.width !== 128 || info.height !== 128)
return exitBecauseInvalid(
connection0,
connection1,
'Image has to be 128x128'
)
const time = Math.floor(Date.now() / 1000)
const codeExists = await db0
.select({ id: verifyCodes.id })
.from(verifyCodes)
.where(
and(
eq(verifyCodes.ip, ip),
eq(verifyCodes.usedTimestamp, 0),
eq(verifyCodes.code, body.verifyCode),
sql`${verifyCodes.timestamp} >= UNIX_TIMESTAMP() - 600`
)
)
.orderBy(desc(verifyCodes.id))
.limit(1)
.execute()
if (codeExists[0]) {
await db0
.update(verifyCodes)
.set({ usedTimestamp: time })
.where(
and(
eq(verifyCodes.id, codeExists[0].id),
eq(verifyCodes.ip, ip),
eq(verifyCodes.usedTimestamp, 0),
eq(verifyCodes.code, body.verifyCode)
)
)
.execute()
} else
return jsonResponse(
{
success: false,
message: 'Invalid verify code (codes can only be used once)'
},
400
)
const hashResult = hash(atob(body.fileContent), 'sha512')
const uuid = crypto.randomUUID()
await db1.insert(berryDashMarketplaceIcons).values({
uuid,
userId,
data: body.fileContent,
hash: hashResult,
price,
name: btoa(body.name),
timestamp: time
})
return jsonResponse({
success: true,
message: 'Icon uploaded successfully! It will be reviewed soon.'
})
}