Add base project files including environment example, license, README, .gitignore, error classes, ESLint config, database modules, texture assets, repositories, routes, schemas, services, and server entry point. This establishes the foundational structure for a Yggdrasil-compatible REST API with modular error handling, database setup, and route organization.
77 lines
2.4 KiB
JavaScript
77 lines
2.4 KiB
JavaScript
const fs = require("node:fs")
|
|
const path = require("node:path")
|
|
const express = require("express")
|
|
const router = express.Router()
|
|
const multer = require("multer")
|
|
const rateLimit = require("express-rate-limit")
|
|
const userService = require("../../../../../services/userService")
|
|
const authService = require("../../../../../services/authService")
|
|
const { DefaultError } = require("../../../../../errors/errors")
|
|
|
|
const TEMP_DIR = path.join(process.cwd(), "data", "temp")
|
|
|
|
if (!fs.existsSync(TEMP_DIR)) {
|
|
fs.mkdirSync(TEMP_DIR, { recursive: true })
|
|
}
|
|
|
|
const upload = multer({
|
|
dest: TEMP_DIR,
|
|
limits: { fileSize: 2 * 1024 * 1024 }
|
|
})
|
|
|
|
const uploadLimiter = rateLimit({
|
|
windowMs: 60 * 1000,
|
|
max: 20,
|
|
standardHeaders: true,
|
|
legacyHeaders: false,
|
|
validate: {
|
|
ip: false
|
|
},
|
|
keyGenerator: (req) => {
|
|
rateLimit.ipKeyGenerator()
|
|
return req.headers.authorization || req.ip
|
|
},
|
|
handler: (req, res, next, options) => {
|
|
throw new DefaultError(429, "Too many requests. Please try again later.")
|
|
}
|
|
})
|
|
|
|
|
|
router.post("/", uploadLimiter, async (req, res, next) => {
|
|
if (req.is('application/json')) {
|
|
try {
|
|
const token = req.headers.authorization.replace("Bearer ", "").trim()
|
|
const player = await authService.verifyAccessToken({ accessToken: token })
|
|
|
|
await userService.uploadSkinFromUrl(player.user.uuid, req.body.url, req.body.variant)
|
|
|
|
return res.status(200).send()
|
|
} catch (err) {
|
|
return next(err)
|
|
}
|
|
}
|
|
|
|
else {
|
|
upload.single("file")(req, res, async (err) => {
|
|
if (err) return next(err)
|
|
try {
|
|
if (!req.headers.authorization) {
|
|
if (req.file) await fs.promises.unlink(req.file.path).catch(() => {})
|
|
throw new DefaultError(401, "Missing Authorization Header")
|
|
}
|
|
|
|
const token = req.headers.authorization.replace("Bearer ", "").trim()
|
|
const player = await authService.verifyAccessToken({ accessToken: token })
|
|
|
|
await userService.uploadSkin(player.user.uuid, req.file, req.body.variant)
|
|
|
|
return res.status(200).send()
|
|
} catch (error) {
|
|
if (req.file) await fs.promises.unlink(req.file.path).catch(() => {})
|
|
return next(error)
|
|
}
|
|
})
|
|
}
|
|
})
|
|
|
|
module.exports = router |