mirror of
https://github.com/linux-odyssey/linux-odyssey.git
synced 2026-11-05 19:20:12 +08:00
Feat global exception (#83)
- add global exceptions - refactor quest models to reusable parts other improvements: - improve error handling
This commit is contained in:
25 files changed
+231
-201
No files matched your search
@@ -0,0 +1,19 @@
|
|||||||
|
import { Schema } from 'mongoose'
|
||||||
|
import conditionSchema from './condition.js'
|
||||||
|
import responseSchema from './response.js'
|
||||||
|
import hintSchema from './hint.js'
|
||||||
|
|
||||||
|
const commandResponderSchema = new Schema({
|
||||||
|
condition: {
|
||||||
|
type: conditionSchema,
|
||||||
|
required: false,
|
||||||
|
},
|
||||||
|
responses: {
|
||||||
|
type: [responseSchema],
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
commandResponderSchema.add(hintSchema)
|
||||||
|
|
||||||
|
export default commandResponderSchema
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
import { Schema } from 'mongoose'
|
||||||
|
|
||||||
|
const fileConditionSchema = new Schema({
|
||||||
|
path: {
|
||||||
|
type: String,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
type: String,
|
||||||
|
exists: {
|
||||||
|
type: Boolean,
|
||||||
|
default: true,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
const conditionSchema = new Schema({
|
||||||
|
command: [String],
|
||||||
|
output: [String],
|
||||||
|
error: [String],
|
||||||
|
pwd: [String],
|
||||||
|
files: [fileConditionSchema],
|
||||||
|
})
|
||||||
|
|
||||||
|
conditionSchema.add({
|
||||||
|
or: [conditionSchema],
|
||||||
|
not: conditionSchema,
|
||||||
|
})
|
||||||
|
|
||||||
|
export default conditionSchema
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
import { Schema } from 'mongoose'
|
||||||
|
import commandResponderSchema from './commandResponder.js'
|
||||||
|
|
||||||
|
const exceptionSchema = new Schema({})
|
||||||
|
|
||||||
|
exceptionSchema.add(commandResponderSchema)
|
||||||
|
|
||||||
|
export default exceptionSchema
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
import { Schema } from 'mongoose'
|
||||||
|
import exceptionSchema from './exception.js'
|
||||||
|
import requirementSchema from './requirement.js'
|
||||||
|
|
||||||
|
const globalExceptionSchema = new Schema({})
|
||||||
|
|
||||||
|
globalExceptionSchema.add(exceptionSchema)
|
||||||
|
globalExceptionSchema.add(requirementSchema)
|
||||||
|
|
||||||
|
export default globalExceptionSchema
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
import { Schema } from 'mongoose'
|
||||||
|
|
||||||
|
const hintSchema = new Schema({
|
||||||
|
hints: {
|
||||||
|
type: [String],
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
export default hintSchema
|
||||||
@@ -1,98 +1,6 @@
|
|||||||
import { model, Schema } from 'mongoose'
|
import { model, Schema } from 'mongoose'
|
||||||
|
import stageSchema from './stage.js'
|
||||||
const responseSchema = new Schema({
|
import globalExceptionSchema from './globalException.js'
|
||||||
type: {
|
|
||||||
type: String,
|
|
||||||
enum: ['narrative', 'dialogue'],
|
|
||||||
default: 'dialogue',
|
|
||||||
required: true,
|
|
||||||
},
|
|
||||||
content: [String],
|
|
||||||
speaker: { type: String, default: 'Ada' },
|
|
||||||
color: String,
|
|
||||||
})
|
|
||||||
|
|
||||||
const fileConditionSchema = new Schema({
|
|
||||||
path: {
|
|
||||||
type: String,
|
|
||||||
required: true,
|
|
||||||
},
|
|
||||||
type: String,
|
|
||||||
exists: {
|
|
||||||
type: Boolean,
|
|
||||||
default: true,
|
|
||||||
},
|
|
||||||
})
|
|
||||||
|
|
||||||
const conditionSchema = new Schema({
|
|
||||||
command: [String],
|
|
||||||
output: [String],
|
|
||||||
error: [String],
|
|
||||||
pwd: [String],
|
|
||||||
files: [fileConditionSchema],
|
|
||||||
})
|
|
||||||
|
|
||||||
conditionSchema.add({
|
|
||||||
or: [conditionSchema],
|
|
||||||
not: conditionSchema,
|
|
||||||
})
|
|
||||||
|
|
||||||
const exceptionSchema = new Schema({
|
|
||||||
condition: {
|
|
||||||
type: conditionSchema,
|
|
||||||
required: false,
|
|
||||||
},
|
|
||||||
catchAll: {
|
|
||||||
type: Boolean,
|
|
||||||
required: true,
|
|
||||||
default: false,
|
|
||||||
},
|
|
||||||
responses: {
|
|
||||||
type: [responseSchema],
|
|
||||||
required: true,
|
|
||||||
},
|
|
||||||
hints: {
|
|
||||||
type: [String],
|
|
||||||
required: true,
|
|
||||||
},
|
|
||||||
})
|
|
||||||
|
|
||||||
const stageSchema = new Schema({
|
|
||||||
id: {
|
|
||||||
type: String,
|
|
||||||
required: true,
|
|
||||||
},
|
|
||||||
task: {
|
|
||||||
type: String,
|
|
||||||
},
|
|
||||||
requirements: [
|
|
||||||
{
|
|
||||||
type: String,
|
|
||||||
required: true,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
repeatable: {
|
|
||||||
type: Boolean,
|
|
||||||
required: true,
|
|
||||||
default: false,
|
|
||||||
},
|
|
||||||
condition: {
|
|
||||||
type: conditionSchema,
|
|
||||||
required: true,
|
|
||||||
},
|
|
||||||
responses: {
|
|
||||||
type: [responseSchema],
|
|
||||||
required: true,
|
|
||||||
},
|
|
||||||
hints: {
|
|
||||||
type: [String],
|
|
||||||
required: true,
|
|
||||||
},
|
|
||||||
exceptions: {
|
|
||||||
type: [exceptionSchema],
|
|
||||||
required: true,
|
|
||||||
},
|
|
||||||
})
|
|
||||||
|
|
||||||
const Quest = model(
|
const Quest = model(
|
||||||
'Quest',
|
'Quest',
|
||||||
@@ -126,6 +34,10 @@ const Quest = model(
|
|||||||
type: [stageSchema],
|
type: [stageSchema],
|
||||||
required: true,
|
required: true,
|
||||||
},
|
},
|
||||||
|
exceptions: {
|
||||||
|
type: [globalExceptionSchema],
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,12 @@
|
|||||||
|
import { Schema } from 'mongoose'
|
||||||
|
|
||||||
|
const requirementSchema = new Schema({
|
||||||
|
requirements: [
|
||||||
|
{
|
||||||
|
type: String,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
})
|
||||||
|
|
||||||
|
export default requirementSchema
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
import { Schema } from 'mongoose'
|
||||||
|
|
||||||
|
const responseSchema = new Schema({
|
||||||
|
type: {
|
||||||
|
type: String,
|
||||||
|
enum: ['narrative', 'dialogue'],
|
||||||
|
default: 'dialogue',
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
content: [String],
|
||||||
|
speaker: { type: String, default: 'Ada' },
|
||||||
|
color: String,
|
||||||
|
})
|
||||||
|
|
||||||
|
export default responseSchema
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
import { Schema } from 'mongoose'
|
||||||
|
import commandResponderSchema from './commandResponder.js'
|
||||||
|
import stageExceptionSchema from './stageException.js'
|
||||||
|
import requirementSchema from './requirement.js'
|
||||||
|
|
||||||
|
const stageSchema = new Schema({
|
||||||
|
id: {
|
||||||
|
type: String,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
task: {
|
||||||
|
type: String,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
exceptions: {
|
||||||
|
type: [stageExceptionSchema],
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
stageSchema.add(commandResponderSchema)
|
||||||
|
stageSchema.add(requirementSchema)
|
||||||
|
|
||||||
|
export default stageSchema
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
import { Schema } from 'mongoose'
|
||||||
|
import exceptionSchema from './exception.js'
|
||||||
|
|
||||||
|
const stageExceptionSchema = new Schema({
|
||||||
|
catchAll: {
|
||||||
|
type: Boolean,
|
||||||
|
required: true,
|
||||||
|
default: false,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
stageExceptionSchema.add(exceptionSchema)
|
||||||
|
|
||||||
|
export default stageExceptionSchema
|
||||||
@@ -9,14 +9,6 @@ requirements:
|
|||||||
- get-started
|
- get-started
|
||||||
|
|
||||||
stages:
|
stages:
|
||||||
- id: warning-01
|
|
||||||
repeatable: true
|
|
||||||
condition:
|
|
||||||
command:
|
|
||||||
- ' '
|
|
||||||
hints:
|
|
||||||
- '!! 請勿使用全形空格 !!'
|
|
||||||
|
|
||||||
- id: start
|
- id: start
|
||||||
task: 輸入 `echo start` 來開始教程
|
task: 輸入 `echo start` 來開始教程
|
||||||
condition:
|
condition:
|
||||||
@@ -73,3 +65,11 @@ stages:
|
|||||||
- 你成功的在終端機上顯示了 `How are you?`。
|
- 你成功的在終端機上顯示了 `How are you?`。
|
||||||
- 相信你已經了解如何使用 `echo` 指令來顯示文字了。
|
- 相信你已經了解如何使用 `echo` 指令來顯示文字了。
|
||||||
- 之後如果要在終端機顯示文字,記得可以使用 `echo` 指令。
|
- 之後如果要在終端機顯示文字,記得可以使用 `echo` 指令。
|
||||||
|
|
||||||
|
exceptions:
|
||||||
|
- repeatable: true
|
||||||
|
condition:
|
||||||
|
command:
|
||||||
|
- ' '
|
||||||
|
hints:
|
||||||
|
- '!! 請勿使用全形空格 !!'
|
||||||
@@ -9,14 +9,6 @@ requirements:
|
|||||||
- create
|
- create
|
||||||
|
|
||||||
stages:
|
stages:
|
||||||
- id: warning-01
|
|
||||||
repeatable: true
|
|
||||||
condition:
|
|
||||||
command:
|
|
||||||
- ' '
|
|
||||||
hints:
|
|
||||||
- '!! 請勿使用全形空格 !!'
|
|
||||||
|
|
||||||
- id: start
|
- id: start
|
||||||
task: 輸入 `echo start` 來開始
|
task: 輸入 `echo start` 來開始
|
||||||
condition:
|
condition:
|
||||||
|
|||||||
@@ -10,14 +10,6 @@ requirements:
|
|||||||
- read
|
- read
|
||||||
|
|
||||||
stages:
|
stages:
|
||||||
- id: warning-01
|
|
||||||
repeatable: true
|
|
||||||
condition:
|
|
||||||
command:
|
|
||||||
- ' '
|
|
||||||
hints:
|
|
||||||
- '!! 請勿使用全形空格 !!'
|
|
||||||
|
|
||||||
- id: start
|
- id: start
|
||||||
task: 輸入 `echo start` 來開始
|
task: 輸入 `echo start` 來開始
|
||||||
condition:
|
condition:
|
||||||
@@ -68,6 +60,7 @@ stages:
|
|||||||
- ➤ 輸入 `cat hello.txt` 來查看 `hello.txt` 檔案的內容
|
- ➤ 輸入 `cat hello.txt` 來查看 `hello.txt` 檔案的內容
|
||||||
|
|
||||||
- id: cat
|
- id: cat
|
||||||
|
task: 使用 `cat` 指令來查看 `hello.txt` 檔案的內容
|
||||||
condition:
|
condition:
|
||||||
command:
|
command:
|
||||||
- ^cat hello.txt$
|
- ^cat hello.txt$
|
||||||
|
|||||||
@@ -0,0 +1,6 @@
|
|||||||
|
exceptions:
|
||||||
|
- condition:
|
||||||
|
command:
|
||||||
|
- '`'
|
||||||
|
hints:
|
||||||
|
- '!! 不需要輸入上下引號 !!'
|
||||||
@@ -10,14 +10,6 @@ requirements:
|
|||||||
- get-started
|
- get-started
|
||||||
|
|
||||||
stages:
|
stages:
|
||||||
- id: warning-01
|
|
||||||
repeatable: true
|
|
||||||
condition:
|
|
||||||
command:
|
|
||||||
- ' '
|
|
||||||
hints:
|
|
||||||
- '!! 請勿使用全形空格 !!'
|
|
||||||
|
|
||||||
- id: start
|
- id: start
|
||||||
task: 輸入 `echo start` 來開始教程
|
task: 輸入 `echo start` 來開始教程
|
||||||
condition:
|
condition:
|
||||||
@@ -115,7 +107,7 @@ stages:
|
|||||||
pwd:
|
pwd:
|
||||||
- ^/home/commander/folder1$
|
- ^/home/commander/folder1$
|
||||||
command:
|
command:
|
||||||
- ls
|
- ^ls$
|
||||||
responses:
|
responses:
|
||||||
- content:
|
- content:
|
||||||
- 你現在可以看到 `folder1` 資料夾中有兩的檔案,分別是 `file1.txt` 跟 `file2.txt`。
|
- 你現在可以看到 `folder1` 資料夾中有兩的檔案,分別是 `file1.txt` 跟 `file2.txt`。
|
||||||
|
|||||||
@@ -9,22 +9,6 @@ instruction: |
|
|||||||
請你在終端機中的 `commander:~ $` 後面輸入 `echo start` 並按下 Enter 來開始教程。
|
請你在終端機中的 `commander:~ $` 後面輸入 `echo start` 並按下 Enter 來開始教程。
|
||||||
|
|
||||||
stages:
|
stages:
|
||||||
- id: warning-01
|
|
||||||
repeatable: true
|
|
||||||
condition:
|
|
||||||
command:
|
|
||||||
- ' '
|
|
||||||
hints:
|
|
||||||
- '!! 請勿使用全形空格 !!'
|
|
||||||
|
|
||||||
- id: warning-02
|
|
||||||
repeatable: true
|
|
||||||
condition:
|
|
||||||
command:
|
|
||||||
- '`'
|
|
||||||
hints:
|
|
||||||
- '!! 不需要輸入上下引號 !!'
|
|
||||||
|
|
||||||
- id: start
|
- id: start
|
||||||
task: 輸入 `echo start` 來開始教程
|
task: 輸入 `echo start` 來開始教程
|
||||||
condition:
|
condition:
|
||||||
|
|||||||
@@ -9,14 +9,6 @@ requirements:
|
|||||||
- create
|
- create
|
||||||
|
|
||||||
stages:
|
stages:
|
||||||
- id: warning-01
|
|
||||||
repeatable: true
|
|
||||||
condition:
|
|
||||||
command:
|
|
||||||
- ' '
|
|
||||||
hints:
|
|
||||||
- '!! 請勿使用全形空格 !!'
|
|
||||||
|
|
||||||
- id: start
|
- id: start
|
||||||
task: 輸入 `echo start` 來開始
|
task: 輸入 `echo start` 來開始
|
||||||
condition:
|
condition:
|
||||||
|
|||||||
@@ -11,14 +11,6 @@ requirements:
|
|||||||
- explore
|
- explore
|
||||||
|
|
||||||
stages:
|
stages:
|
||||||
- id: warning-01
|
|
||||||
repeatable: true
|
|
||||||
condition:
|
|
||||||
command:
|
|
||||||
- ' '
|
|
||||||
hints:
|
|
||||||
- '!! 請勿使用全形空格 !!'
|
|
||||||
|
|
||||||
- id: start
|
- id: start
|
||||||
task: 輸入 `echo start` 來開始教程
|
task: 輸入 `echo start` 來開始教程
|
||||||
condition:
|
condition:
|
||||||
|
|||||||
@@ -9,14 +9,6 @@ requirements:
|
|||||||
- path
|
- path
|
||||||
|
|
||||||
stages:
|
stages:
|
||||||
- id: warning-01
|
|
||||||
repeatable: true
|
|
||||||
condition:
|
|
||||||
command:
|
|
||||||
- ' '
|
|
||||||
hints:
|
|
||||||
- '!! 請勿使用全形空格 !!'
|
|
||||||
|
|
||||||
- id: start
|
- id: start
|
||||||
task: 輸入 `echo start` 來開始教程
|
task: 輸入 `echo start` 來開始教程
|
||||||
condition:
|
condition:
|
||||||
|
|||||||
@@ -13,14 +13,6 @@ requirements:
|
|||||||
- move
|
- move
|
||||||
|
|
||||||
stages:
|
stages:
|
||||||
- id: warning-01
|
|
||||||
repeatable: true
|
|
||||||
condition:
|
|
||||||
command:
|
|
||||||
- ' '
|
|
||||||
hints:
|
|
||||||
- '!! 請勿使用全形空格 !!'
|
|
||||||
|
|
||||||
- id: start
|
- id: start
|
||||||
task: 輸入 `echo start` 來開始
|
task: 輸入 `echo start` 來開始
|
||||||
condition:
|
condition:
|
||||||
|
|||||||
@@ -56,7 +56,7 @@ export default class CommandHandler extends SessionHandler {
|
|||||||
this.session.graph = graph
|
this.session.graph = graph
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
logger.error('Update file graph failed', {
|
logger.error('Update file graph failed', {
|
||||||
error: e,
|
error: e.message,
|
||||||
session: this.session.id,
|
session: this.session.id,
|
||||||
graph: this.session.graph,
|
graph: this.session.graph,
|
||||||
discover: this.params.discover,
|
discover: this.params.discover,
|
||||||
@@ -122,9 +122,9 @@ export default class CommandHandler extends SessionHandler {
|
|||||||
async run() {
|
async run() {
|
||||||
try {
|
try {
|
||||||
this.quest = await Quest.findById(this.session.quest)
|
this.quest = await Quest.findById(this.session.quest)
|
||||||
const stages = this.getStages()
|
const stages = this.getUnlockedComponents(this.quest.stages)
|
||||||
if (stages.length === 0) {
|
if (stages.length === 0) {
|
||||||
logger.warn('stage not found', this.session.tasks)
|
logger.warn('stage not found', { session: this.session.id })
|
||||||
return {}
|
return {}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -136,16 +136,7 @@ export default class CommandHandler extends SessionHandler {
|
|||||||
)
|
)
|
||||||
|
|
||||||
const stage = stages.find((_, i) => matches[i])
|
const stage = stages.find((_, i) => matches[i])
|
||||||
if (!stage) {
|
if (stage) {
|
||||||
for (const s of stages) {
|
|
||||||
// eslint-disable-next-line no-await-in-loop
|
|
||||||
const exception = await this.checkException(s)
|
|
||||||
if (exception) {
|
|
||||||
return this.executeException(exception)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return {}
|
|
||||||
}
|
|
||||||
const response = this.execute(stage)
|
const response = this.execute(stage)
|
||||||
|
|
||||||
return {
|
return {
|
||||||
@@ -153,10 +144,36 @@ export default class CommandHandler extends SessionHandler {
|
|||||||
end: this.session.status === 'finished',
|
end: this.session.status === 'finished',
|
||||||
...response,
|
...response,
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// stage exception
|
||||||
|
for (const s of stages) {
|
||||||
|
// eslint-disable-next-line no-await-in-loop
|
||||||
|
const exception = await this.checkException(s)
|
||||||
|
if (exception) {
|
||||||
|
return this.executeException(exception)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// global exception
|
||||||
|
if (this.quest.exceptions.length > 0) {
|
||||||
|
const globalExceptions = this.getUnlockedComponents(
|
||||||
|
this.quest.exceptions
|
||||||
|
)
|
||||||
|
for (const exception of globalExceptions) {
|
||||||
|
// eslint-disable-next-line no-await-in-loop
|
||||||
|
if (await this.isMatch(exception.condition)) {
|
||||||
|
return this.executeException(exception)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return {}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
logger.error('CommandHandler error', {
|
logger.error('CommandHandler error', {
|
||||||
error: err,
|
error: err.message,
|
||||||
session: this.session.id,
|
session: this.session._id,
|
||||||
|
quest: this.quest._id,
|
||||||
command: this.commandInput,
|
command: this.commandInput,
|
||||||
})
|
})
|
||||||
return {}
|
return {}
|
||||||
|
|||||||
@@ -19,16 +19,14 @@ export default class SessionHandler {
|
|||||||
return task ? task.completed : false
|
return task ? task.completed : false
|
||||||
}
|
}
|
||||||
|
|
||||||
getStages() {
|
getUnlockedComponents(components) {
|
||||||
return this.quest.stages.filter(
|
return components.filter((component) =>
|
||||||
(stage) =>
|
component.requirements.every((r) => this.isCompleted({ id: r }))
|
||||||
stage.requirements.every((r) => this.isCompleted({ id: r })) &&
|
|
||||||
(stage.repeatable || !this.isCompleted(stage))
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
getNewTasks() {
|
getNewTasks() {
|
||||||
const stages = this.getStages()
|
const stages = this.getUnlockedComponents(this.quest.stages)
|
||||||
return stages
|
return stages
|
||||||
.filter((s) => s.task)
|
.filter((s) => s.task)
|
||||||
.filter((s) => !this.session.tasks.some((t) => t.id === s.id))
|
.filter((s) => !this.session.tasks.some((t) => t.id === s.id))
|
||||||
|
|||||||
@@ -38,7 +38,12 @@ async function main() {
|
|||||||
await setupTest()
|
await setupTest()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
await loadAndUpdateQuests()
|
await loadAndUpdateQuests()
|
||||||
|
} catch (err) {
|
||||||
|
logger.error('Failed to load quests', err)
|
||||||
|
return
|
||||||
|
}
|
||||||
expiryRemovalScheduler()
|
expiryRemovalScheduler()
|
||||||
|
|
||||||
const file = await fs.readFile('./swagger.yaml', 'utf8')
|
const file = await fs.readFile('./swagger.yaml', 'utf8')
|
||||||
|
|||||||
@@ -1,9 +1,13 @@
|
|||||||
import winston from 'winston'
|
import winston, { format } from 'winston'
|
||||||
import config from '../config.js'
|
import config from '../config.js'
|
||||||
|
|
||||||
const logger = winston.createLogger({
|
const logger = winston.createLogger({
|
||||||
level: 'info',
|
level: 'info',
|
||||||
format: winston.format.json(),
|
format: format.combine(
|
||||||
|
format.errors({ stack: true }),
|
||||||
|
format.timestamp(),
|
||||||
|
format.json()
|
||||||
|
),
|
||||||
defaultMeta: { service: 'backend', timestamp: new Date().toISOString() },
|
defaultMeta: { service: 'backend', timestamp: new Date().toISOString() },
|
||||||
transports: [
|
transports: [
|
||||||
//
|
//
|
||||||
|
|||||||
@@ -7,6 +7,15 @@ import logger from './logger.js'
|
|||||||
|
|
||||||
const questDirectory = path.join(process.cwd(), '..', 'quests')
|
const questDirectory = path.join(process.cwd(), '..', 'quests')
|
||||||
|
|
||||||
|
class QuestValidationError extends Error {
|
||||||
|
constructor(message, questId, error) {
|
||||||
|
super(message)
|
||||||
|
this.name = 'QuestValidationError'
|
||||||
|
this.questId = questId
|
||||||
|
this.error = error
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
async function mapQuests(callback) {
|
async function mapQuests(callback) {
|
||||||
const questNames = await fs.readdir(questDirectory, {
|
const questNames = await fs.readdir(questDirectory, {
|
||||||
withFileTypes: true,
|
withFileTypes: true,
|
||||||
@@ -23,12 +32,20 @@ async function mapQuests(callback) {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function loadGlobalExceptions() {
|
||||||
|
const exceptionsPath = path.join(questDirectory, 'exceptions.yml')
|
||||||
|
const body = await fs.readFile(exceptionsPath, 'utf-8')
|
||||||
|
return yaml.parse(body)
|
||||||
|
}
|
||||||
|
|
||||||
export async function loadAndUpdateQuests() {
|
export async function loadAndUpdateQuests() {
|
||||||
await Quest.deleteMany({})
|
await Quest.deleteMany({})
|
||||||
|
const { exceptions } = await loadGlobalExceptions()
|
||||||
|
console.log(exceptions)
|
||||||
return mapQuests(async (id, questPath) => {
|
return mapQuests(async (id, questPath) => {
|
||||||
const files = await fs.readdir(questPath)
|
const files = await fs.readdir(questPath)
|
||||||
if (!files.includes('game.yml')) {
|
if (!files.includes('game.yml')) {
|
||||||
throw new Error(`Quest ${id} is missing game.yml`)
|
throw new QuestValidationError(`Missing game.yml`, id)
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@@ -44,10 +61,12 @@ export async function loadAndUpdateQuests() {
|
|||||||
image,
|
image,
|
||||||
...questData,
|
...questData,
|
||||||
})
|
})
|
||||||
|
quest.exceptions.push(...exceptions)
|
||||||
await quest.save()
|
await quest.save()
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logger.error(`Error parsing quest ${id}:`, error)
|
console.error(error)
|
||||||
throw error
|
logger.error(`Error parsing quest`, { id, error })
|
||||||
|
throw new QuestValidationError(`Error parsing quest`, id, error)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in new issue
Block a user