Build installer and management site from spec

This commit is contained in:
2026-05-07 23:22:34 +09:00
parent 0b061e63b7
commit af6e559682
33 changed files with 7125 additions and 1 deletions

143
src/server/routes/op.ts Normal file
View File

@@ -0,0 +1,143 @@
import { Router } from 'express'
import { fetchReleaseVersions } from '../../shared/mojang'
import {
createNewPack,
deletePacks,
loadAccounts,
loadPackDefinition,
loadRootManifest,
normalizePackDefinition,
updatePack
} from '../../shared/store'
import { requireAuth } from '../middleware/auth'
export const opRouter = Router()
function pickFirstValue(value: unknown): string {
if (Array.isArray(value)) {
return typeof value[0] === 'string' ? value[0] : ''
}
return typeof value === 'string' ? value : ''
}
opRouter.get('/op', (req, res) => {
if (req.session.userId != null) {
res.redirect('/op/dashboard')
return
}
res.render('op/login', {
errorMessage: null
})
})
opRouter.post('/op/login', async (req, res, next) => {
try {
const { id, password } = req.body as { id?: string; password?: string }
const accounts = await loadAccounts()
const matched = accounts.find((entry) => entry.id === id && entry.password === password)
if (matched == null) {
res.status(401).render('op/login', {
errorMessage: '아이디 또는 비밀번호가 올바르지 않습니다.'
})
return
}
req.session.userId = matched.id
res.redirect('/op/dashboard')
} catch (error) {
next(error)
}
})
opRouter.post('/op/logout', requireAuth, (req, res) => {
req.session.destroy(() => {
res.redirect('/op')
})
})
opRouter.get('/op/dashboard', requireAuth, async (_req, res, next) => {
try {
const manifest = await loadRootManifest()
res.render('op/dashboard', {
userId: _req.session.userId,
packs: manifest.packs
})
} catch (error) {
next(error)
}
})
opRouter.post('/op/dashboard/packs', requireAuth, async (_req, res, next) => {
try {
const packKey = await createNewPack()
res.redirect(`/op/dashboard/${packKey}`)
} catch (error) {
next(error)
}
})
opRouter.post('/op/dashboard/packs/delete', requireAuth, async (req, res, next) => {
try {
const rawSelection = req.body.packKeys
const packKeys = Array.isArray(rawSelection)
? rawSelection.map(String)
: typeof rawSelection === 'string'
? [rawSelection]
: []
await deletePacks(packKeys)
res.redirect('/op/dashboard')
} catch (error) {
next(error)
}
})
opRouter.get('/op/dashboard/:packName', requireAuth, async (req, res, next) => {
try {
const packName = pickFirstValue(req.params.packName)
const definition = await loadPackDefinition(packName)
if (definition == null) {
res.status(404).send('서버팩 JSON을 찾을 수 없습니다.')
return
}
const rootManifest = await loadRootManifest()
const packEntry = rootManifest.packs.find((entry) => entry.file === packName)
const releases = await fetchReleaseVersions()
res.render('op/editor', {
userId: req.session.userId,
packKey: packName,
packEntry,
pack: definition,
releases
})
} catch (error) {
next(error)
}
})
opRouter.post('/op/dashboard/:packName', requireAuth, async (req, res, next) => {
try {
const packKey = pickFirstValue(req.params.packName)
const nextPackName = pickFirstValue(req.body.displayName).trim() || packKey
const nextJsonKey = pickFirstValue(req.body.fileName).trim() || packKey
const normalized = normalizePackDefinition({
mcVersion: pickFirstValue(req.body.mcVersion),
serverMinRam: Number(pickFirstValue(req.body.serverMinRam)),
serverMaxRam: Number(pickFirstValue(req.body.serverMaxRam)),
clientMinRam: Number(pickFirstValue(req.body.clientMinRam)),
clientRecommendedRam: Number(pickFirstValue(req.body.clientRecommendedRam)),
packPath: pickFirstValue(req.body.packPath),
description: pickFirstValue(req.body.description)
})
const changedKey = await updatePack(packKey, nextPackName, nextJsonKey, normalized)
res.redirect(`/op/dashboard/${changedKey}`)
} catch (error) {
next(error)
}
})