fix: wrap all async controller with error handler

* add validations to newCommand
This commit is contained in:
wancat authored and Lin, Hung-Shin committed 2023-11-14 10:45:24 +08:00
1 parent 1af85651b8
commit c2dac8d385
13 files changed
+120 -120

No files matched your search

+1 -1
View File
@@ -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()
}) })
} }
+9 -1
View File
@@ -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>
+7 -3
View File
@@ -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)
} }
} }
+19 -28
View File
@@ -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 })
} })
+31 -49
View File
@@ -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 })
}
}
+9 -9
View File
@@ -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,
}) })
} })
+2 -1
View File
@@ -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()
+5 -5
View File
@@ -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
} }
+6
View File
@@ -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