Add client apply flow and asset uploads
This commit is contained in:
@@ -1,5 +1,10 @@
|
||||
import { Router } from 'express'
|
||||
import multer from 'multer'
|
||||
import path from 'node:path'
|
||||
import fs from 'node:fs'
|
||||
import fsp from 'node:fs/promises'
|
||||
import { fetchReleaseVersions } from '../../shared/mojang'
|
||||
import { fileDir } from '../../shared/paths'
|
||||
import {
|
||||
createNewPack,
|
||||
deletePacks,
|
||||
@@ -8,11 +13,14 @@ import {
|
||||
loadPackDefinition,
|
||||
loadRootManifest,
|
||||
normalizePackDefinition,
|
||||
savePackDefinition,
|
||||
updatePack
|
||||
} from '../../shared/store'
|
||||
import { PackDefinition } from '../../shared/types'
|
||||
import { requireAuth } from '../middleware/auth'
|
||||
|
||||
export const opRouter = Router()
|
||||
const upload = multer({ storage: multer.memoryStorage() })
|
||||
|
||||
function pickFirstValue(value: unknown): string {
|
||||
if (Array.isArray(value)) {
|
||||
@@ -21,6 +29,41 @@ function pickFirstValue(value: unknown): string {
|
||||
return typeof value === 'string' ? value : ''
|
||||
}
|
||||
|
||||
function sanitizeUploadFileName(name: string): string {
|
||||
return name.replace(/[^a-zA-Z0-9._-]/g, '-').replace(/-+/g, '-')
|
||||
}
|
||||
|
||||
function normalizeAssetPathForWeb(filePath: string): string {
|
||||
return filePath.replace(/\\/g, '/')
|
||||
}
|
||||
|
||||
async function saveUploadedPackAsset(packKey: string, bucket: 'loaders' | 'resourcepacks' | 'shaderpacks', file: Express.Multer.File): Promise<string> {
|
||||
const safeName = sanitizeUploadFileName(file.originalname)
|
||||
const relativePath = path.join('uploads', packKey, bucket, `${Date.now()}-${safeName}`)
|
||||
const absolutePath = path.join(fileDir, relativePath)
|
||||
await fsp.mkdir(path.dirname(absolutePath), { recursive: true })
|
||||
await fsp.writeFile(absolutePath, file.buffer)
|
||||
return normalizeAssetPathForWeb(relativePath)
|
||||
}
|
||||
|
||||
async function mutatePackDefinition(packKey: string, mutate: (pack: PackDefinition) => void): Promise<void> {
|
||||
const current = await loadPackDefinition(packKey)
|
||||
if (current == null) {
|
||||
throw new Error('서버팩 JSON을 찾을 수 없습니다.')
|
||||
}
|
||||
|
||||
const next = normalizePackDefinition(current)
|
||||
mutate(next)
|
||||
await savePackDefinition(packKey, next)
|
||||
}
|
||||
|
||||
async function removeUploadedAsset(relativePath: string): Promise<void> {
|
||||
const absolutePath = path.join(fileDir, relativePath)
|
||||
if (fs.existsSync(absolutePath)) {
|
||||
await fsp.unlink(absolutePath)
|
||||
}
|
||||
}
|
||||
|
||||
opRouter.get('/op', (req, res) => {
|
||||
if (req.session.userId != null) {
|
||||
res.redirect('/op/dashboard')
|
||||
@@ -125,10 +168,18 @@ opRouter.post('/op/dashboard/:packName', requireAuth, async (req, res, next) =>
|
||||
const packKey = pickFirstValue(req.params.packName)
|
||||
const nextPackName = pickFirstValue(req.body.displayName).trim() || packKey
|
||||
const nextJsonKey = pickFirstValue(req.body.fileName).trim() || packKey
|
||||
const currentDefinition = await loadPackDefinition(packKey)
|
||||
if (currentDefinition == null) {
|
||||
throw new Error('서버팩 JSON을 찾을 수 없습니다.')
|
||||
}
|
||||
|
||||
const normalized = normalizePackDefinition({
|
||||
...currentDefinition,
|
||||
mcVersion: pickFirstValue(req.body.mcVersion),
|
||||
recommendedJdkVersion: Number(pickFirstValue(req.body.recommendedJdkVersion)),
|
||||
loaderType: pickFirstValue(req.body.loaderType) as PackDefinition['loaderType'],
|
||||
loaderVersion: pickFirstValue(req.body.loaderVersion),
|
||||
loaderInstallerPath: pickFirstValue(req.body.loaderInstallerPath),
|
||||
serverMinRam: Number(pickFirstValue(req.body.serverMinRam)),
|
||||
serverMaxRam: Number(pickFirstValue(req.body.serverMaxRam)),
|
||||
clientMinRam: Number(pickFirstValue(req.body.clientMinRam)),
|
||||
@@ -143,3 +194,101 @@ opRouter.post('/op/dashboard/:packName', requireAuth, async (req, res, next) =>
|
||||
next(error)
|
||||
}
|
||||
})
|
||||
|
||||
opRouter.post('/op/dashboard/:packName/assets/loader', requireAuth, upload.single('asset'), async (req, res, next) => {
|
||||
try {
|
||||
const packKey = pickFirstValue(req.params.packName)
|
||||
if (req.file == null) {
|
||||
throw new Error('업로드된 로더 파일이 없습니다.')
|
||||
}
|
||||
|
||||
const relativePath = await saveUploadedPackAsset(packKey, 'loaders', req.file)
|
||||
await mutatePackDefinition(packKey, (pack) => {
|
||||
pack.loaderInstallerPath = relativePath
|
||||
})
|
||||
|
||||
res.redirect(`/op/dashboard/${packKey}`)
|
||||
} catch (error) {
|
||||
next(error)
|
||||
}
|
||||
})
|
||||
|
||||
opRouter.post('/op/dashboard/:packName/assets/resource-pack', requireAuth, upload.single('asset'), async (req, res, next) => {
|
||||
try {
|
||||
const packKey = pickFirstValue(req.params.packName)
|
||||
if (req.file == null) {
|
||||
throw new Error('업로드된 리소스팩 파일이 없습니다.')
|
||||
}
|
||||
|
||||
const relativePath = await saveUploadedPackAsset(packKey, 'resourcepacks', req.file)
|
||||
await mutatePackDefinition(packKey, (pack) => {
|
||||
pack.resourcePackFiles = [...(pack.resourcePackFiles ?? []), relativePath]
|
||||
})
|
||||
|
||||
res.redirect(`/op/dashboard/${packKey}`)
|
||||
} catch (error) {
|
||||
next(error)
|
||||
}
|
||||
})
|
||||
|
||||
opRouter.post('/op/dashboard/:packName/assets/shader-pack', requireAuth, upload.single('asset'), async (req, res, next) => {
|
||||
try {
|
||||
const packKey = pickFirstValue(req.params.packName)
|
||||
if (req.file == null) {
|
||||
throw new Error('업로드된 쉐이더 파일이 없습니다.')
|
||||
}
|
||||
|
||||
const relativePath = await saveUploadedPackAsset(packKey, 'shaderpacks', req.file)
|
||||
await mutatePackDefinition(packKey, (pack) => {
|
||||
pack.shaderPackFiles = [...(pack.shaderPackFiles ?? []), relativePath]
|
||||
})
|
||||
|
||||
res.redirect(`/op/dashboard/${packKey}`)
|
||||
} catch (error) {
|
||||
next(error)
|
||||
}
|
||||
})
|
||||
|
||||
opRouter.post('/op/dashboard/:packName/assets/loader/remove', requireAuth, async (req, res, next) => {
|
||||
try {
|
||||
const packKey = pickFirstValue(req.params.packName)
|
||||
const targetPath = pickFirstValue(req.body.assetPath)
|
||||
await removeUploadedAsset(targetPath)
|
||||
await mutatePackDefinition(packKey, (pack) => {
|
||||
if (pack.loaderInstallerPath === targetPath) {
|
||||
pack.loaderInstallerPath = ''
|
||||
}
|
||||
})
|
||||
res.redirect(`/op/dashboard/${packKey}`)
|
||||
} catch (error) {
|
||||
next(error)
|
||||
}
|
||||
})
|
||||
|
||||
opRouter.post('/op/dashboard/:packName/assets/resource-pack/remove', requireAuth, async (req, res, next) => {
|
||||
try {
|
||||
const packKey = pickFirstValue(req.params.packName)
|
||||
const targetPath = pickFirstValue(req.body.assetPath)
|
||||
await removeUploadedAsset(targetPath)
|
||||
await mutatePackDefinition(packKey, (pack) => {
|
||||
pack.resourcePackFiles = (pack.resourcePackFiles ?? []).filter((entry) => entry !== targetPath)
|
||||
})
|
||||
res.redirect(`/op/dashboard/${packKey}`)
|
||||
} catch (error) {
|
||||
next(error)
|
||||
}
|
||||
})
|
||||
|
||||
opRouter.post('/op/dashboard/:packName/assets/shader-pack/remove', requireAuth, async (req, res, next) => {
|
||||
try {
|
||||
const packKey = pickFirstValue(req.params.packName)
|
||||
const targetPath = pickFirstValue(req.body.assetPath)
|
||||
await removeUploadedAsset(targetPath)
|
||||
await mutatePackDefinition(packKey, (pack) => {
|
||||
pack.shaderPackFiles = (pack.shaderPackFiles ?? []).filter((entry) => entry !== targetPath)
|
||||
})
|
||||
res.redirect(`/op/dashboard/${packKey}`)
|
||||
} catch (error) {
|
||||
next(error)
|
||||
}
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user