mirror of
https://github.com/linux-odyssey/linux-odyssey.git
synced 2026-11-05 19:20:12 +08:00
fix: wrap all async controller with error handler
* add validations to newCommand
This commit is contained in:
1 parent
1af85651b8
commit
c2dac8d385
13 files changed
+120
-120
No files matched your search
@@ -23,7 +23,7 @@ class SocketWrapper {
|
|||||||
this.socket.once('terminal', () => {
|
this.socket.once('terminal', () => {
|
||||||
resolve()
|
resolve()
|
||||||
})
|
})
|
||||||
this.socket.once('connect_error', reject)
|
this.socket.on('connect_error', reject)
|
||||||
this.bindListeners()
|
this.bindListeners()
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
<script setup>
|
<script setup>
|
||||||
import { computed, onMounted } from 'vue'
|
import { computed, onMounted } from 'vue'
|
||||||
|
import { useToast } from 'vue-toastification'
|
||||||
import sessionStore, { init } from '../store/session'
|
import sessionStore, { init } from '../store/session'
|
||||||
import GameHeaderPart from '../components/GameHeaderPart.vue'
|
import GameHeaderPart from '../components/GameHeaderPart.vue'
|
||||||
import CommandlistPart from '../components/CommandlistPart.vue'
|
import CommandlistPart from '../components/CommandlistPart.vue'
|
||||||
@@ -21,8 +22,15 @@ const props = defineProps({
|
|||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const toast = useToast()
|
||||||
|
|
||||||
onMounted(async () => {
|
onMounted(async () => {
|
||||||
await init(props.questId)
|
try {
|
||||||
|
await init(props.questId)
|
||||||
|
} catch (err) {
|
||||||
|
console.error(err)
|
||||||
|
toast.error('Failed to initialize game session')
|
||||||
|
}
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|||||||
@@ -48,7 +48,7 @@ async function main() {
|
|||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
const additionalData = await handleCommand(CMD_NAME)
|
const params = await handleCommand(CMD_NAME)
|
||||||
|
|
||||||
const output = await readOrNone(CMD_OUTPUT_FILE)
|
const output = await readOrNone(CMD_OUTPUT_FILE)
|
||||||
const error = await readOrNone(CMD_ERROR_FILE)
|
const error = await readOrNone(CMD_ERROR_FILE)
|
||||||
@@ -58,7 +58,7 @@ async function main() {
|
|||||||
error,
|
error,
|
||||||
exit_code: CMD_EXIT_CODE,
|
exit_code: CMD_EXIT_CODE,
|
||||||
pwd: PWD,
|
pwd: PWD,
|
||||||
...additionalData,
|
params,
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
const res = await api.post('/commands', payload)
|
const res = await api.post('/commands', payload)
|
||||||
@@ -77,7 +77,11 @@ async function main() {
|
|||||||
console.log(colorize('Quest completed!', 'blue'))
|
console.log(colorize('Quest completed!', 'blue'))
|
||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error(err)
|
if (err.response) {
|
||||||
|
console.error(err.response.data)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
console.error(err.message)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,8 +1,9 @@
|
|||||||
import { matchedData } from 'express-validator'
|
import { matchedData } from 'express-validator'
|
||||||
import { User } from '@linux-odyssey/models'
|
import { User } from '@linux-odyssey/models'
|
||||||
import { genJWT, hashPassword } from '../../utils/auth.js'
|
import { genJWT, hashPassword } from '../../utils/auth.js'
|
||||||
|
import { asyncHandler } from '../../middleware/error.js'
|
||||||
|
|
||||||
export async function issueToken(req, res) {
|
export const issueToken = asyncHandler(async (req, res) => {
|
||||||
const { user } = req
|
const { user } = req
|
||||||
const token = await genJWT({
|
const token = await genJWT({
|
||||||
_id: user.id,
|
_id: user.id,
|
||||||
@@ -10,34 +11,27 @@ export async function issueToken(req, res) {
|
|||||||
email: user.email,
|
email: user.email,
|
||||||
})
|
})
|
||||||
res.json({ token })
|
res.json({ token })
|
||||||
}
|
})
|
||||||
|
|
||||||
export async function register(req, res, next) {
|
export const register = asyncHandler(async (req, res, next) => {
|
||||||
const { username, password, email } = matchedData(req)
|
const { username, password, email } = matchedData(req)
|
||||||
|
|
||||||
const user = new User({
|
const user = new User({
|
||||||
username,
|
username,
|
||||||
email,
|
email,
|
||||||
})
|
})
|
||||||
try {
|
user.hashedPassword = await hashPassword(password)
|
||||||
user.hashedPassword = await hashPassword(password)
|
await user.save()
|
||||||
await user.save()
|
req.login(user, (err) => {
|
||||||
req.login(user, (err) => {
|
if (err) {
|
||||||
if (err) {
|
next(err)
|
||||||
next(err)
|
return
|
||||||
return
|
}
|
||||||
}
|
res.status(201).json({
|
||||||
res.status(201).json({
|
message: 'user created',
|
||||||
message: 'user created',
|
|
||||||
})
|
|
||||||
})
|
})
|
||||||
} catch (err) {
|
})
|
||||||
console.error(err)
|
})
|
||||||
res.status(500).json({
|
|
||||||
message: 'error registering user',
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export function logout(req, res) {
|
export function logout(req, res) {
|
||||||
req.logout((err) => {
|
req.logout((err) => {
|
||||||
@@ -60,7 +54,7 @@ export function checkSession(req, res) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function socialLogin(req, res) {
|
export function socialLogin(req, res) {
|
||||||
const { newUser } = req.user
|
const { newUser } = req.user
|
||||||
if (newUser) {
|
if (newUser) {
|
||||||
req.session.newUser = newUser
|
req.session.newUser = newUser
|
||||||
@@ -70,7 +64,7 @@ export async function socialLogin(req, res) {
|
|||||||
res.redirect('/')
|
res.redirect('/')
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function registerFromSession(req, res) {
|
export const registerFromSession = asyncHandler(async (req, res, next) => {
|
||||||
const { username } = matchedData(req)
|
const { username } = matchedData(req)
|
||||||
const { newUser } = req.session
|
const { newUser } = req.session
|
||||||
|
|
||||||
@@ -88,10 +82,7 @@ export async function registerFromSession(req, res) {
|
|||||||
|
|
||||||
req.login(user, (err) => {
|
req.login(user, (err) => {
|
||||||
if (err) {
|
if (err) {
|
||||||
console.error(err)
|
next(err)
|
||||||
res.status(500).json({
|
|
||||||
message: 'error logging in',
|
|
||||||
})
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
delete req.session.newUser
|
delete req.session.newUser
|
||||||
@@ -99,4 +90,4 @@ export async function registerFromSession(req, res) {
|
|||||||
message: 'user created',
|
message: 'user created',
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
}
|
})
|
||||||
@@ -1,27 +1,21 @@
|
|||||||
import { Command, Session } from '@linux-odyssey/models'
|
import { Command, Session } from '@linux-odyssey/models'
|
||||||
|
import { matchedData } from 'express-validator'
|
||||||
import CommandHandler from '../../game/commandHandler.js'
|
import CommandHandler from '../../game/commandHandler.js'
|
||||||
import { pushToSession } from '../socket.js'
|
import { pushToSession } from '../socket.js'
|
||||||
import { finishSession } from '../../game/sessionManager.js'
|
import { finishSession } from '../../game/sessionManager.js'
|
||||||
|
import { asyncHandler } from '../../middleware/error.js'
|
||||||
|
|
||||||
const commandCompleteCallbacks = new Map()
|
const commandCompleteCallbacks = new Map()
|
||||||
|
|
||||||
export async function newCommand(req, res) {
|
export const newCommand = asyncHandler(async (req, res) => {
|
||||||
const { command, pwd, output, error, ...additionalData } = req.body
|
const { command, pwd, output, error, params } = matchedData(req)
|
||||||
|
|
||||||
if (!command) {
|
|
||||||
res.status(400).json({ message: 'command is required' })
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
const { sessionId } = req.user
|
const { sessionId } = req.user
|
||||||
|
|
||||||
const session = await Session.findById(sessionId)
|
const session = await Session.findById(sessionId)
|
||||||
|
|
||||||
if (!session) {
|
if (!session) {
|
||||||
res.status(404).json({ message: 'session not found' })
|
res.status(404).json({ message: 'session not found' })
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if (session.status === 'finished') {
|
if (session.status === 'finished') {
|
||||||
// Allow commands to be sent to finished sessions
|
// Allow commands to be sent to finished sessions
|
||||||
res.status(200).end()
|
res.status(200).end()
|
||||||
@@ -44,10 +38,8 @@ export async function newCommand(req, res) {
|
|||||||
|
|
||||||
await c.save()
|
await c.save()
|
||||||
|
|
||||||
const commandHandler = new CommandHandler(session, c, additionalData)
|
const commandHandler = new CommandHandler(session, c, params)
|
||||||
|
|
||||||
const response = await commandHandler.run()
|
const response = await commandHandler.run()
|
||||||
|
|
||||||
if (response) {
|
if (response) {
|
||||||
c.stage = response.stage
|
c.stage = response.stage
|
||||||
await c.save()
|
await c.save()
|
||||||
@@ -61,9 +53,9 @@ export async function newCommand(req, res) {
|
|||||||
|
|
||||||
res.status(201).json(response)
|
res.status(201).json(response)
|
||||||
await session.save()
|
await session.save()
|
||||||
}
|
})
|
||||||
|
|
||||||
export async function completedCommand(req, res) {
|
export const completedCommand = asyncHandler(async (req, res) => {
|
||||||
const { sessionId } = req.user
|
const { sessionId } = req.user
|
||||||
|
|
||||||
const session = await Session.findById(sessionId)
|
const session = await Session.findById(sessionId)
|
||||||
@@ -86,4 +78,4 @@ export async function completedCommand(req, res) {
|
|||||||
callback()
|
callback()
|
||||||
commandCompleteCallbacks.delete(session.id)
|
commandCompleteCallbacks.delete(session.id)
|
||||||
res.json({ message: 'ok' })
|
res.json({ message: 'ok' })
|
||||||
}
|
})
|
||||||
@@ -1,7 +1,8 @@
|
|||||||
import { Quest } from '@linux-odyssey/models'
|
import { Quest } from '@linux-odyssey/models'
|
||||||
import { matchedData } from 'express-validator'
|
import { matchedData } from 'express-validator'
|
||||||
|
import { asyncHandler } from '../../middleware/error.js'
|
||||||
|
|
||||||
export async function getQuests(req, res) {
|
export const getQuests = asyncHandler(async (req, res) => {
|
||||||
const quests = await Quest.find({})
|
const quests = await Quest.find({})
|
||||||
res.json(
|
res.json(
|
||||||
quests.map(({ title, _id, requirements }) => ({
|
quests.map(({ title, _id, requirements }) => ({
|
||||||
@@ -10,9 +11,9 @@ export async function getQuests(req, res) {
|
|||||||
requirements,
|
requirements,
|
||||||
}))
|
}))
|
||||||
)
|
)
|
||||||
}
|
})
|
||||||
|
|
||||||
export async function getQuestDetail(req, res) {
|
export const getQuestDetail = asyncHandler(async (req, res) => {
|
||||||
const { questId } = matchedData(req)
|
const { questId } = matchedData(req)
|
||||||
const quest = await Quest.findById(questId)
|
const quest = await Quest.findById(questId)
|
||||||
if (!quest) {
|
if (!quest) {
|
||||||
@@ -21,4 +22,4 @@ export async function getQuestDetail(req, res) {
|
|||||||
}
|
}
|
||||||
const { _id, title, order, instruction } = quest
|
const { _id, title, order, instruction } = quest
|
||||||
res.json({ _id, title, order, instruction })
|
res.json({ _id, title, order, instruction })
|
||||||
}
|
})
|
||||||
@@ -4,6 +4,7 @@ import {
|
|||||||
createNewSession,
|
createNewSession,
|
||||||
getOrCreateActiveSession,
|
getOrCreateActiveSession,
|
||||||
} from '../../game/sessionManager.js'
|
} from '../../game/sessionManager.js'
|
||||||
|
import { asyncHandler } from '../../middleware/error.js'
|
||||||
|
|
||||||
function sessionSummary(session) {
|
function sessionSummary(session) {
|
||||||
return {
|
return {
|
||||||
@@ -25,59 +26,40 @@ function sessionDetail(session) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function getSessionList(req, res) {
|
export const getSessionList = asyncHandler(async (req, res) => {
|
||||||
try {
|
const query = { user: req.user._id }
|
||||||
const query = { user: req.user._id }
|
const { questId, status } = matchedData(req)
|
||||||
const { questId, status } = matchedData(req)
|
if (questId) {
|
||||||
if (questId) {
|
query.quest = questId
|
||||||
query.quest = questId
|
|
||||||
}
|
|
||||||
query.status = status || 'active'
|
|
||||||
const sessions = await Session.find(query)
|
|
||||||
res.json(sessions.map(sessionSummary))
|
|
||||||
} catch (err) {
|
|
||||||
console.error(err)
|
|
||||||
res.status(500).json({ message: err.message })
|
|
||||||
}
|
}
|
||||||
}
|
query.status = status || 'active'
|
||||||
|
const sessions = await Session.find(query)
|
||||||
|
res.json(sessions.map(sessionSummary))
|
||||||
|
})
|
||||||
|
|
||||||
export async function createSession(req, res) {
|
export const createSession = asyncHandler(async (req, res) => {
|
||||||
const { questId } = matchedData(req)
|
const { questId } = matchedData(req)
|
||||||
try {
|
const session = await createNewSession(req.user, questId)
|
||||||
const session = await createNewSession(req.user, questId)
|
res.status(201).json(sessionDetail(session))
|
||||||
res.status(201).json(sessionDetail(session))
|
})
|
||||||
} catch (err) {
|
|
||||||
console.error(err)
|
export const getSessionById = asyncHandler(async (req, res) => {
|
||||||
res.status(500).json({ message: err.message })
|
const { sessionId } = matchedData(req)
|
||||||
|
const session = await Session.findOne({
|
||||||
|
_id: sessionId,
|
||||||
|
user: req.user._id,
|
||||||
|
})
|
||||||
|
|
||||||
|
if (!session) {
|
||||||
|
res.status(404).json({ message: 'Session not found.' })
|
||||||
|
return
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
export async function getSessionById(req, res) {
|
res.json(sessionDetail(session))
|
||||||
try {
|
})
|
||||||
const { sessionId } = matchedData(req)
|
|
||||||
const session = await Session.findOne({
|
|
||||||
_id: sessionId,
|
|
||||||
user: req.user._id,
|
|
||||||
})
|
|
||||||
|
|
||||||
if (!session) {
|
export const getOrCreateSession = asyncHandler(async (req, res) => {
|
||||||
res.status(404).json({ message: 'Session not found.' })
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
res.json(sessionDetail(session))
|
|
||||||
} catch (err) {
|
|
||||||
res.status(500).json({ message: err.message })
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function getOrCreateSession(req, res) {
|
|
||||||
const { questId } = matchedData(req)
|
const { questId } = matchedData(req)
|
||||||
try {
|
const session = await getOrCreateActiveSession(req.user, questId)
|
||||||
const session = await getOrCreateActiveSession(req.user, questId)
|
res.json(sessionDetail(session))
|
||||||
res.json(sessionDetail(session))
|
})
|
||||||
} catch (err) {
|
|
||||||
console.error(err)
|
|
||||||
res.status(500).json({ message: err.message })
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,20 +1,20 @@
|
|||||||
import { UserProfile } from '@linux-odyssey/models'
|
import { UserProfile } from '@linux-odyssey/models'
|
||||||
|
import { asyncHandler } from '../../middleware/error.js'
|
||||||
|
|
||||||
export async function me(req, res) {
|
export const me = asyncHandler(async (req, res) => {
|
||||||
const userProfile = await UserProfile.findOne({
|
const userProfile = await UserProfile.findOne({
|
||||||
user: req.user._id,
|
user: req.user._id,
|
||||||
}).populate('user')
|
})
|
||||||
if (!userProfile) {
|
if (!userProfile) {
|
||||||
res.status(404).json({ message: 'User not found' })
|
throw new Error(
|
||||||
return
|
`UserProfile not found for user ${req.user.username} ${req.user._id}!`
|
||||||
|
)
|
||||||
}
|
}
|
||||||
const {
|
const { username, email } = req.user
|
||||||
user: { username, email },
|
const { progress } = userProfile
|
||||||
progress,
|
|
||||||
} = userProfile
|
|
||||||
res.json({
|
res.json({
|
||||||
username,
|
username,
|
||||||
email,
|
email,
|
||||||
progress,
|
progress,
|
||||||
})
|
})
|
||||||
}
|
})
|
||||||
@@ -1,9 +1,10 @@
|
|||||||
import { Router } from 'express'
|
import { Router } from 'express'
|
||||||
import * as commandController from '../controllers/commandController.js'
|
import * as commandController from '../controllers/commandController.js'
|
||||||
|
import { newCommandValidator } from '../validators/commandValidator.js'
|
||||||
|
|
||||||
const router = Router()
|
const router = Router()
|
||||||
|
|
||||||
router.post('/', commandController.newCommand)
|
router.post('/', newCommandValidator, commandController.newCommand)
|
||||||
router.post('/completed', commandController.completedCommand)
|
router.post('/completed', commandController.completedCommand)
|
||||||
|
|
||||||
export default router
|
export default router
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
import { body } from 'express-validator'
|
||||||
|
import { noError } from '../../middleware/validator.js'
|
||||||
|
|
||||||
|
const checkCommand = () => body('command').isString().notEmpty().trim()
|
||||||
|
const checkPwd = () => body('pwd').isString().notEmpty().trim()
|
||||||
|
const checkOutput = () => body('output').trim().optional()
|
||||||
|
const checkError = () => body('error').trim().optional()
|
||||||
|
const checkParams = () => body('params').isObject().optional()
|
||||||
|
|
||||||
|
export const newCommandValidator = [
|
||||||
|
checkCommand(),
|
||||||
|
checkPwd(),
|
||||||
|
checkOutput(),
|
||||||
|
checkError(),
|
||||||
|
checkParams(),
|
||||||
|
noError,
|
||||||
|
]
|
||||||
@@ -3,9 +3,7 @@ import { checkQuestId } from './questValidator.js'
|
|||||||
import { noError } from '../../middleware/validator.js'
|
import { noError } from '../../middleware/validator.js'
|
||||||
|
|
||||||
const checkSessionStatus = () =>
|
const checkSessionStatus = () =>
|
||||||
check('status')
|
check('status').isString().isIn(['active', 'inactive', 'finished'])
|
||||||
.isString()
|
|
||||||
.custom((value) => ['active', 'inactive', 'finished'].includes(value))
|
|
||||||
|
|
||||||
export const checkSessionId = () =>
|
export const checkSessionId = () =>
|
||||||
check('sessionId').notEmpty().isString().isMongoId()
|
check('sessionId').notEmpty().isString().isMongoId()
|
||||||
|
|||||||
@@ -15,16 +15,16 @@ const checkMatch = (pattern, input) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export default class CommandHandler extends SessionHandler {
|
export default class CommandHandler extends SessionHandler {
|
||||||
constructor(session, commandInput, additionalData) {
|
constructor(session, commandInput, params) {
|
||||||
super(session)
|
super(session)
|
||||||
this.commandInput = commandInput
|
this.commandInput = commandInput
|
||||||
this.argv = minimist(this.commandInput.command.split(' '))
|
this.argv = minimist(this.commandInput.command.split(' '))
|
||||||
|
|
||||||
this.additionalData = additionalData
|
this.params = params
|
||||||
}
|
}
|
||||||
|
|
||||||
handleEvent() {
|
handleEvent() {
|
||||||
if (this.additionalData.discover) this.discoverHandler()
|
if (this.params.discover) this.discoverHandler()
|
||||||
}
|
}
|
||||||
|
|
||||||
handleCommand() {
|
handleCommand() {
|
||||||
@@ -39,7 +39,7 @@ export default class CommandHandler extends SessionHandler {
|
|||||||
case 'ls':
|
case 'ls':
|
||||||
pushToSession(this.session.id, 'graph', {
|
pushToSession(this.session.id, 'graph', {
|
||||||
pwd: this.commandInput.pwd,
|
pwd: this.commandInput.pwd,
|
||||||
discover: this.additionalData.discover,
|
discover: this.params.discover,
|
||||||
})
|
})
|
||||||
break
|
break
|
||||||
|
|
||||||
@@ -50,7 +50,7 @@ export default class CommandHandler extends SessionHandler {
|
|||||||
|
|
||||||
async discoverHandler() {
|
async discoverHandler() {
|
||||||
const graph = new FileGraph(this.session.graph)
|
const graph = new FileGraph(this.session.graph)
|
||||||
graph.discover(this.additionalData.discover)
|
graph.discover(this.params.discover)
|
||||||
this.session.graph = graph
|
this.session.graph = graph
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,3 +1,9 @@
|
|||||||
|
export function asyncHandler(fn) {
|
||||||
|
return (req, res, next) => {
|
||||||
|
fn(req, res, next).catch(next)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// eslint-disable-next-line no-unused-vars
|
// eslint-disable-next-line no-unused-vars
|
||||||
export default function errorHandler(err, req, res, next) {
|
export default function errorHandler(err, req, res, next) {
|
||||||
console.error(err.stack) // Log error stack trace to the console
|
console.error(err.stack) // Log error stack trace to the console
|
||||||
|
|||||||
Reference in new issue
Block a user