mirror of
https://github.com/linux-odyssey/linux-odyssey.git
synced 2026-11-06 03:20:13 +08:00
feat: combine task with stage
This commit is contained in:
1 parent
de8d49aea3
commit
dd7829b3e9
8 files changed
+127
-54
No files matched your search
+10
-10
@@ -5,9 +5,9 @@ import { defaultUser, genSessionJWT } from '../utils/auth.js'
|
||||
|
||||
const sessions = new Map()
|
||||
|
||||
function listenToSession(sessionId, event, callback) {
|
||||
const callbacks = sessions.get((sessionId, event)) || []
|
||||
sessions.set((sessionId, event), callbacks.concat(callback))
|
||||
function listenToSession(sessionId, callback) {
|
||||
const callbacks = sessions.get(sessionId) || []
|
||||
sessions.set(sessionId, callbacks.concat(callback))
|
||||
}
|
||||
|
||||
function removeFromSession(sessionId, event, callback) {
|
||||
@@ -18,10 +18,10 @@ function removeFromSession(sessionId, event, callback) {
|
||||
)
|
||||
}
|
||||
|
||||
export function pushToSession(sessionId, event, data) {
|
||||
const callbacks = sessions.get((sessionId, event))
|
||||
export function pushToSession(sessionId, ...args) {
|
||||
const callbacks = sessions.get(sessionId)
|
||||
if (callbacks) {
|
||||
callbacks.forEach((callback) => callback(data))
|
||||
callbacks.forEach((callback) => callback(...args))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -68,16 +68,16 @@ async function connectClient(socket) {
|
||||
stream.socket.write(message)
|
||||
})
|
||||
|
||||
const graphCallback = (data) => {
|
||||
socket.emit('graph', data)
|
||||
const socketCallback = (event, data) => {
|
||||
socket.emit(event, data)
|
||||
}
|
||||
|
||||
listenToSession(session.id, 'graph', graphCallback)
|
||||
listenToSession(session.id, socketCallback)
|
||||
|
||||
socket.on('disconnect', () => {
|
||||
console.log('Disconnected from the client.')
|
||||
stream.socket.write('exit\n')
|
||||
removeFromSession(session.id, 'graph', graphCallback)
|
||||
removeFromSession(session.id, socketCallback)
|
||||
stream.destroy()
|
||||
})
|
||||
|
||||
|
||||
@@ -15,7 +15,7 @@ const containerOptions = {
|
||||
Binds:
|
||||
!config.isProduction && config.hostPwd
|
||||
? [
|
||||
`${config.hostPwd}/quests/helloworld/home:/home/rudeus`,
|
||||
`${config.hostPwd}/quests/helloworld/home:/home/commander`,
|
||||
`${config.hostPwd}/packages/container:/usr/local/lib/container`,
|
||||
]
|
||||
: [],
|
||||
|
||||
@@ -5,7 +5,7 @@ import { pushToSession } from '../api/socket.js'
|
||||
|
||||
// 檢查 pattern 是否符合 input
|
||||
const checkMatch = (pattern, input) => {
|
||||
if (pattern.length === 0) return true
|
||||
if (!pattern || pattern.length === 0) return true
|
||||
return pattern.some((p) => {
|
||||
const regex = new RegExp(p)
|
||||
return regex.test(input)
|
||||
@@ -52,37 +52,53 @@ export default class CommandHandler {
|
||||
this.session.graph = graph
|
||||
}
|
||||
|
||||
getTask(stage) {
|
||||
return this.session.tasks.find((t) => t.id === stage.id)
|
||||
}
|
||||
|
||||
isCompleted(stage) {
|
||||
const task = this.getTask(stage)
|
||||
return task ? task.completed : false
|
||||
}
|
||||
|
||||
getStages() {
|
||||
return this.quest.stages.filter((s) =>
|
||||
s.requirements.every((r) => this.session.completed.includes(r))
|
||||
return this.quest.stages.filter(
|
||||
(stage) =>
|
||||
stage.requirements.every((r) => this.isCompleted({ id: r })) &&
|
||||
(stage.repeatable || !this.isCompleted(stage))
|
||||
)
|
||||
}
|
||||
|
||||
matchStage(stage) {
|
||||
const commandMatch = checkMatch(
|
||||
stage.condition.command,
|
||||
this.commandInput.command
|
||||
)
|
||||
const outputMatch = checkMatch(
|
||||
stage.condition.output,
|
||||
this.commandInput.output
|
||||
)
|
||||
const errorMatch = checkMatch(
|
||||
stage.condition.error,
|
||||
this.commandInput.output
|
||||
isMatch(stage) {
|
||||
const keys = ['command', 'output', 'error', 'pwd']
|
||||
return keys.every((k) =>
|
||||
checkMatch(stage.condition[k], this.commandInput[k])
|
||||
)
|
||||
}
|
||||
|
||||
if (!commandMatch || !outputMatch || !errorMatch) return {}
|
||||
|
||||
this.session.completed.push(stage.id)
|
||||
execute(stage) {
|
||||
this.session.tasks
|
||||
.filter((t) => t.id === stage.id)
|
||||
.forEach((t) => {
|
||||
t.completed = true
|
||||
})
|
||||
this.session.hints.push(...stage.hints)
|
||||
if (stage.id === 'END') {
|
||||
this.session.status = 'finished'
|
||||
this.session.finishedAt = new Date()
|
||||
}
|
||||
const stages = this.getStages()
|
||||
console.debug(stages)
|
||||
const newTasks = stages
|
||||
.filter((s) => s.task)
|
||||
.map((s) => ({ id: s.id, name: s.task, completed: false }))
|
||||
|
||||
this.session.tasks.push(...newTasks)
|
||||
pushToSession(this.session.id, 'tasks', this.session.tasks)
|
||||
return {
|
||||
responses: stage.responses,
|
||||
hints: stage.hints,
|
||||
tasks: newTasks,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -90,18 +106,18 @@ export default class CommandHandler {
|
||||
this.quest = await Quest.findById(this.session.quest)
|
||||
const stages = this.getStages()
|
||||
if (stages.length === 0) {
|
||||
console.error('stage not found', this.session.progress)
|
||||
console.error('stage not found', this.session.tasks)
|
||||
return {}
|
||||
}
|
||||
|
||||
this.handleEvent()
|
||||
this.handleCommand()
|
||||
|
||||
const response = stages.reduce(
|
||||
(r, s) => ({ ...r, ...this.matchStage(s) }),
|
||||
{}
|
||||
)
|
||||
const response = stages
|
||||
.filter((s) => this.isMatch(s))
|
||||
.reduce((r, s) => ({ ...r, ...this.execute(s) }), {})
|
||||
|
||||
console.log(this.session)
|
||||
return {
|
||||
end: this.session.status === 'finished',
|
||||
...response,
|
||||
|
||||
+24
-16
@@ -15,25 +15,25 @@ const stageSchema = new Schema({
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
task: {
|
||||
type: String,
|
||||
},
|
||||
requirements: [
|
||||
{
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
],
|
||||
repeatable: {
|
||||
type: Boolean,
|
||||
required: true,
|
||||
default: false,
|
||||
},
|
||||
condition: {
|
||||
command: {
|
||||
type: [String],
|
||||
required: false,
|
||||
},
|
||||
output: {
|
||||
type: [String],
|
||||
required: false,
|
||||
},
|
||||
error: {
|
||||
type: [String],
|
||||
required: false,
|
||||
},
|
||||
command: [String],
|
||||
output: [String],
|
||||
error: [String],
|
||||
pwd: [String],
|
||||
},
|
||||
responses: {
|
||||
type: [responseSchema],
|
||||
@@ -43,10 +43,6 @@ const stageSchema = new Schema({
|
||||
type: [String],
|
||||
required: true,
|
||||
},
|
||||
next: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
})
|
||||
|
||||
const Quest = model(
|
||||
@@ -56,6 +52,18 @@ const Quest = model(
|
||||
title: String,
|
||||
order: Number,
|
||||
instruction: String,
|
||||
tasks: {
|
||||
id: String,
|
||||
content: String,
|
||||
display: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
completed: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
},
|
||||
stages: [stageSchema],
|
||||
})
|
||||
)
|
||||
|
||||
@@ -14,6 +14,21 @@ nodeSchema.add({
|
||||
children: [nodeSchema],
|
||||
})
|
||||
|
||||
const taskSchema = new Schema({
|
||||
id: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
name: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
completed: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
})
|
||||
|
||||
const Session = model(
|
||||
'Session',
|
||||
new Schema(
|
||||
@@ -39,7 +54,7 @@ const Session = model(
|
||||
default: Date.now,
|
||||
required: true,
|
||||
},
|
||||
completed: [String],
|
||||
tasks: [taskSchema],
|
||||
hints: [String],
|
||||
graph: {
|
||||
type: nodeSchema,
|
||||
|
||||
Reference in new issue
Block a user