diff --git a/.github/workflows/ci_cd.yml b/.github/workflows/ci_cd.yml
index c240cd8..5440a2e 100644
--- a/.github/workflows/ci_cd.yml
+++ b/.github/workflows/ci_cd.yml
@@ -1,12 +1,32 @@
-name: CI/CD Workflow
+name: CI/CD
on:
push:
- branches:
- - main
jobs:
+ ci:
+ runs-on: ubuntu-latest
+
+ steps:
+ - name: Checkout
+ uses: actions/checkout@v3
+
+ - uses: actions/setup-node@v3
+ with:
+ node-version: 20
+
+ - name: Install dependencies
+ run: yarn install
+
+ - name: Lint
+ run: yarn lint
+
+ - name: Test
+ run: yarn test
+
build-and-push:
+ needs: ci
+ if: github.ref == 'refs/heads/main'
runs-on: ubuntu-latest
steps:
diff --git a/.github/workflows/eslint.yml b/.github/workflows/eslint.yml
deleted file mode 100644
index 01f475e..0000000
--- a/.github/workflows/eslint.yml
+++ /dev/null
@@ -1,22 +0,0 @@
-name: ESLint Checks
-
-on:
- push:
-
-jobs:
- lint:
- runs-on: ubuntu-latest
-
- steps:
- - name: Checkout
- uses: actions/checkout@v3
-
- - uses: actions/setup-node@v3
- with:
- node-version: 20
-
- - name: Install dependencies
- run: yarn install
-
- - name: Lint
- run: yarn lint
diff --git a/app/Dockerfile b/app/Dockerfile
index dd77845..30a17f7 100644
--- a/app/Dockerfile
+++ b/app/Dockerfile
@@ -1,14 +1,18 @@
# Build stage
FROM node as build-stage
WORKDIR /app
-COPY app/package.json yarn.lock ./
-RUN yarn install
-COPY app .
+COPY package.json yarn.lock ./
+COPY app/package.json ./app/package.json
+COPY packages/file-graph/package.json ./packages/file-graph/package.json
+RUN yarn install --frozen-lockfile
+
+COPY app ./app
+COPY packages/file-graph ./packages/file-graph
RUN yarn build
# Production stage
FROM nginx:stable-alpine as production-stage
-COPY --from=build-stage /app/dist /usr/share/nginx/html
+COPY --from=build-stage /app/app/dist /usr/share/nginx/html
COPY deployment/nginx.conf /etc/nginx/conf.d/default.conf
EXPOSE 80
CMD ["nginx", "-g", "daemon off;"]
diff --git a/app/Dockerfile.dev b/app/Dockerfile.dev
index 61d9568..a58a1cc 100644
--- a/app/Dockerfile.dev
+++ b/app/Dockerfile.dev
@@ -1,7 +1,10 @@
FROM node
WORKDIR /app
-COPY app/package.json yarn.lock ./
+COPY package.json yarn.lock ./
+COPY app/package.json ./app/package.json
+COPY packages/file-graph/package.json ./packages/file-graph/package.json
RUN yarn install --frozen-lockfile
-COPY app .
+
+WORKDIR /app/app
CMD [ "yarn", "dev:docker", "--host", "0.0.0.0" ]
\ No newline at end of file
diff --git a/app/package.json b/app/package.json
index d38cd0b..1f168c2 100644
--- a/app/package.json
+++ b/app/package.json
@@ -11,6 +11,7 @@
"preview": "vite preview"
},
"dependencies": {
+ "@linux-odyssey/file-graph": "*",
"@fortawesome/fontawesome-svg-core": "^6.4.0",
"@fortawesome/free-brands-svg-icons": "^6.4.0",
"@fortawesome/free-regular-svg-icons": "^6.4.0",
diff --git a/app/src/App.vue b/app/src/App.vue
index e6e3717..3c94c4f 100644
--- a/app/src/App.vue
+++ b/app/src/App.vue
@@ -20,6 +20,9 @@ onMounted(async () => {
console.log('Disconnected from the server.')
})
socket.on('message', console.log)
+ socket.on('graph', (event) => {
+ sessionManager.handleGraphUpdate(event)
+ })
})
diff --git a/app/src/components/FileNode.vue b/app/src/components/FileNode.vue
new file mode 100644
index 0000000..ee4636e
--- /dev/null
+++ b/app/src/components/FileNode.vue
@@ -0,0 +1,29 @@
+
+ {{ node.discovered ? node.name : '???' }}
+
+
+
+
diff --git a/app/src/components/TreeChart.vue b/app/src/components/TreeChart.vue
index f5ab0e6..93c77b3 100644
--- a/app/src/components/TreeChart.vue
+++ b/app/src/components/TreeChart.vue
@@ -1,56 +1,16 @@
-
+
@@ -149,17 +109,18 @@
-moz-transition: all 0.5s;
}
-.tree li a:hover,
-.tree li a:hover + ul li a {
+a.discovered {
background: #c8e4f8;
color: #000;
border: 1px solid #94a0b4;
}
-.tree li a:hover + ul li::after,
-.tree li a:hover + ul li::before,
-.tree li a:hover + ul::before,
-.tree li a:hover + ul ul::before {
+ul.discovered,
+li.discovered {
border-color: #94a0b4;
}
+
+a.pwd {
+ box-shadow: 0 0 10px rgba(255, 255, 255, 0.5); /* 水平偏移、垂直偏移、模糊半徑、顏色 */
+}
diff --git a/app/src/utils/session.js b/app/src/utils/session.js
index 80ab25c..b731d43 100644
--- a/app/src/utils/session.js
+++ b/app/src/utils/session.js
@@ -1,10 +1,19 @@
import { ref } from 'vue'
+import { FileGraph } from '@linux-odyssey/file-graph'
import api from './api'
class SessionManager {
constructor(questId) {
this.questId = questId
this.session = ref(null)
+ this.graph = ref(
+ new FileGraph({
+ path: '/',
+ type: 'folder',
+ discovered: false,
+ })
+ )
+ this.pwd = ref('')
}
getSession() {
@@ -14,6 +23,16 @@ class SessionManager {
setSession(session) {
console.log('Update session:', session)
this.session.value = session
+ this.graph.value = new FileGraph(session.graph)
+ }
+
+ handleGraphUpdate(event) {
+ if (event.discover) {
+ this.graph.value.discover(event.discover)
+ }
+ if (event.pwd) {
+ this.pwd.value = event.pwd
+ }
}
async createSession() {
@@ -37,7 +56,8 @@ class SessionManager {
if (sessions.length === 0) {
return
}
- const session = sessions[sessions.length - 1]
+ const { _id: sessionId } = sessions[sessions.length - 1]
+ const { data: session } = await api.get(`/sessions/${sessionId}`)
console.log(
`Last Session ID: ${session._id}, Quest: ${session.quest}, Created At: ${session.createdAt}`
)
@@ -49,6 +69,7 @@ class SessionManager {
if (!this.session.value) {
await this.createSession()
}
+ console.log(this.session.value)
}
}
diff --git a/docker-compose.yml b/docker-compose.yml
index e5b2474..3492bab 100644
--- a/docker-compose.yml
+++ b/docker-compose.yml
@@ -9,7 +9,8 @@ services:
networks:
- internal
volumes:
- - ./app/src:/app/src
+ - ./app:/app/app
+ - ./packages/file-graph:/app/packages/file-graph
backend:
build:
context: .
@@ -21,8 +22,9 @@ services:
- '3000:3000'
volumes:
- /var/run/docker.sock:/var/run/docker.sock
- - ./server/src:/app/src
- - ./server/swagger.yaml:/app/swagger.yaml
+ - ./server/src:/app/server/src
+ - ./server/swagger.yaml:/app/server/swagger.yaml
+ - ./packages:/app/packages
- ./quests:/app/quests
environment:
- MONGO_URL=mongodb://db:27017/odyssey-test
diff --git a/package.json b/package.json
index 3713418..cc4fd76 100644
--- a/package.json
+++ b/package.json
@@ -13,10 +13,12 @@
"scripts": {
"app": "yarn workspace app dev",
"app:local": "yarn workspace app dev:local",
+ "build": "yarn workspace app build",
"server": "yarn workspace server dev",
"cli": "yarn workspace client start",
"lint": "eslint --ext .js,.vue .",
"format": "prettier --ignore-path .prettierignore --write app server",
+ "test": "yarn node --experimental-vm-modules $(yarn bin jest)",
"clean": "yarn workspace server clean"
},
"devDependencies": {
diff --git a/packages/file-graph/file.js b/packages/file-graph/file.js
index 986e280..983cbeb 100644
--- a/packages/file-graph/file.js
+++ b/packages/file-graph/file.js
@@ -2,7 +2,7 @@ import { basename } from './utils.js'
export default class File {
constructor({ path, type, discovered }) {
- this.path = path
+ this.path = path.trimEnd('/')
this.name = basename(path)
this.type = type
this.discovered = discovered
@@ -13,6 +13,9 @@ export default class File {
}
contains(file) {
- return this.isDirectory() && file.path.startsWith(this.path)
+ if (!this.isDirectory()) return false
+ if (this.path === file.path) return true
+ const parentPath = this.path.endsWith('/') ? this.path : `${this.path}/`
+ return file.path.startsWith(parentPath)
}
}
diff --git a/packages/file-graph/file.test.js b/packages/file-graph/file.test.js
new file mode 100644
index 0000000..175ac73
--- /dev/null
+++ b/packages/file-graph/file.test.js
@@ -0,0 +1,49 @@
+import File from './file.js'
+
+describe('File', () => {
+ let file
+ beforeEach(() => {
+ file = new File({ path: '/home/user', type: 'folder', discovered: true })
+ })
+
+ const filesToCompare = [
+ {
+ name: 'direct children',
+ path: '/home/user/file1.txt',
+ answer: true,
+ },
+ {
+ name: 'nested children',
+ path: '/home/user/folder1/file1.txt',
+ answer: true,
+ },
+ {
+ name: 'outer file',
+ path: '/etc/file1.txt',
+ answer: false,
+ },
+ {
+ name: 'fake children',
+ path: '/home/user2/file2.txt',
+ answer: false,
+ },
+ {
+ name: 'same',
+ path: '/home/user',
+ answer: true,
+ },
+ {
+ name: 'same with trailing',
+ path: '/home/user/',
+ answer: true,
+ },
+ ]
+
+ filesToCompare.forEach(({ name, path, answer }) => {
+ test(`${name}: ${path}`, () => {
+ const fileToCompare = new File({ path, type: 'file', discovered: true })
+ console.log(fileToCompare)
+ expect(file.contains(fileToCompare)).toBe(answer)
+ })
+ })
+})
diff --git a/packages/file-graph/fileGraph.js b/packages/file-graph/fileGraph.js
index 611fc4c..a2a924e 100644
--- a/packages/file-graph/fileGraph.js
+++ b/packages/file-graph/fileGraph.js
@@ -1,17 +1,13 @@
import FileNode from './fileNode.js'
-export default class FileGraph {
- constructor(root) {
- this.root = new FileNode(root)
- }
-
+export default class FileGraph extends FileNode {
add(files) {
// Logic to add the file node to the graph
// You can implement the logic to find the appropriate location in the file graph
// If the file node already exists, throw an error
files.forEach((file) => {
- this.root.addChild(file, { makeParents: true })
+ this.addChild(file, { makeParents: true })
})
}
@@ -21,7 +17,7 @@ export default class FileGraph {
// If the file node doesn't exist, throw an error
files.forEach((file) => {
- this.root.removeChild(file)
+ this.removeChild(file)
})
}
@@ -36,7 +32,7 @@ export default class FileGraph {
newNode.addChild(file, { makeParents: true })
})
- this.root.merge(newNode)
+ this.merge(newNode)
}
handleEvent(event) {
diff --git a/packages/file-graph/fileGraph.test.js b/packages/file-graph/fileGraph.test.js
index 7f6447a..cff996a 100644
--- a/packages/file-graph/fileGraph.test.js
+++ b/packages/file-graph/fileGraph.test.js
@@ -30,9 +30,9 @@ describe('FileGraph', () => {
fileGraph.add(filesToAdd)
- console.log(fileGraph.root.toString())
- expect(fileGraph.root.children.length).toBe(1)
- expect(fileGraph.root.children[0].children[0].children.length).toBe(2)
+ console.log(fileGraph.toString())
+ expect(fileGraph.children.length).toBe(1)
+ expect(fileGraph.children[0].children[0].children.length).toBe(2)
})
test('remove files from graph', () => {
@@ -76,14 +76,44 @@ describe('FileGraph', () => {
fileGraph.remove(filesToRemove)
- console.log(fileGraph.root.toString())
- expect(fileGraph.root.children[0].children[0].children.length).toBe(1)
- expect(fileGraph.root.children[0].children[0].children[0].name).toBe(
- 'folder1'
+ console.log(fileGraph.toString())
+ expect(fileGraph.children[0].children[0].children.length).toBe(1)
+ expect(fileGraph.children[0].children[0].children[0].name).toBe('folder1')
+ expect(fileGraph.children[0].children[0].children[0].children.length).toBe(
+ 0
+ )
+ })
+
+ test('discover', () => {
+ const fileGraph = new FileGraph(root)
+ const discoverFiles = [
+ {
+ path: '/home/user/folder1',
+ type: 'folder',
+ discovered: true,
+ },
+ {
+ path: '/home/user/folder1/file1.txt',
+ type: 'file',
+ discovered: true,
+ },
+ {
+ path: '/home/user/folder1/file2.txt',
+ type: 'file',
+ discovered: true,
+ },
+ {
+ path: '/home/user/folder1/file3.txt',
+ type: 'file',
+ discovered: true,
+ },
+ ]
+
+ fileGraph.discover(discoverFiles)
+ console.log(fileGraph.toString())
+ expect(fileGraph.children[0].children[0].children[0].children.length).toBe(
+ 3
)
- expect(
- fileGraph.root.children[0].children[0].children[0].children.length
- ).toBe(0)
})
test('discover new files', () => {
@@ -113,7 +143,7 @@ describe('FileGraph', () => {
]
fileGraph.add(filesToAdd)
- console.log(fileGraph.root.toString())
+ console.log(fileGraph.toString())
const newFiles = [
{
@@ -140,10 +170,10 @@ describe('FileGraph', () => {
fileGraph.discover(newFiles)
- console.log(fileGraph.root.toString())
- expect(fileGraph.root.children[0].children[0].children.length).toBe(2)
- expect(
- fileGraph.root.children[0].children[0].children[0].children.length
- ).toBe(3)
+ console.log(fileGraph.toString())
+ expect(fileGraph.children[0].children[0].children.length).toBe(2)
+ expect(fileGraph.children[0].children[0].children[0].children.length).toBe(
+ 3
+ )
})
})
diff --git a/packages/file-graph/fileNode.js b/packages/file-graph/fileNode.js
index 96fc60c..cf8f089 100644
--- a/packages/file-graph/fileNode.js
+++ b/packages/file-graph/fileNode.js
@@ -9,7 +9,8 @@ import { basename, dirname } from './utils.js'
export default class FileNode extends File {
constructor(file) {
super(file)
- this.children = file.children || []
+ this.children = []
+ if (file.children) this.children = file.children.map((f) => new FileNode(f))
}
addChild(fileInput, { makeParents } = {}) {
@@ -76,7 +77,12 @@ export default class FileNode extends File {
// Merge the children of the provided file node into this node
// If a child already exists, merge the children of that child
// If a child does not exist, add it to this node
+
+ // If this is the same node, merge the children
if (fileNode.path === this.path) {
+ // Update the state of this node
+ this.discovered = this.discovered || fileNode.discovered
+
fileNode.children.forEach((child) => {
const existingChild = this.children.find((c) => c.path === child.path)
if (existingChild) {
@@ -92,12 +98,17 @@ export default class FileNode extends File {
fileNode.children.some((c) => c.path === child.path)
)
}
- } else if (this.contains(fileNode)) {
+ }
+ // If this contains the parent node
+ else if (this.contains(fileNode)) {
const childNode = this.children.find((child) => child.contains(fileNode))
+ // If a child node exists, merge the provided node into the child node
if (childNode) {
childNode.merge(fileNode)
- } else {
- this.addChild(fileNode)
+ }
+ // If a child node does not exist, add the provided node as a child
+ else {
+ this.addChild(fileNode, { makeParents: true })
}
} else {
throw new Error(
@@ -108,12 +119,15 @@ export default class FileNode extends File {
}
}
- toString() {
- const indent = ' '.repeat(this.path.split('/').length - 1)
- const suffix = this.isDirectory() ? '/' : ''
+ toString(level = 0) {
+ let { name } = this
+ const indent = ' '.repeat(level)
+ name = `${indent}${name}`
+ if (this.isDirectory()) name += '/'
+ if (!this.discovered) name += `*`
const childrenString = this.children
- .map((child) => child.toString())
+ .map((child) => child.toString(level + 1))
.join('')
- return `${indent}${this.name}${suffix}\n${childrenString}`
+ return `${name}\n${childrenString}`
}
}
diff --git a/packages/file-graph/fileNode.test.js b/packages/file-graph/fileNode.test.js
index b58e38f..3413dc5 100644
--- a/packages/file-graph/fileNode.test.js
+++ b/packages/file-graph/fileNode.test.js
@@ -193,6 +193,35 @@ describe('FileNode', () => {
mergeTest(files1, files2, expected)
})
+ test('merge-hidden', () => {
+ const root = {
+ path: '/',
+ type: 'folder',
+ }
+ const files1 = [
+ root,
+ { path: '/a', type: 'file', discovered: true },
+ { path: '/.b', type: 'file', discovered: false },
+ { path: '/c', type: 'folder', discovered: true },
+ { path: '/c/.d', type: 'file', discovered: false },
+ ]
+
+ const files2 = [
+ { path: '/c', type: 'folder', discovered: true },
+ { path: '/c/.d', type: 'file', discovered: true },
+ ]
+
+ const expected = [
+ root,
+ { path: '/a', type: 'file', discovered: true },
+ { path: '/.b', type: 'file', discovered: false },
+ { path: '/c', type: 'folder', discovered: true },
+ { path: '/c/.d', type: 'file', discovered: true },
+ ]
+
+ mergeTest(files1, files2, expected)
+ })
+
test('remove-child', () => {
const root = {
path: '/',
@@ -219,3 +248,39 @@ describe('FileNode', () => {
removeChildTest(files, fileToRemove, expected)
})
})
+
+test('load nested fileNode', () => {
+ const data = {
+ path: '/',
+ type: 'folder',
+ children: [
+ {
+ path: '/a',
+ type: 'file',
+ },
+ {
+ path: '/b',
+ type: 'folder',
+ children: [
+ {
+ path: '/b/c',
+ type: 'folder',
+ children: [
+ {
+ path: '/b/c/d',
+ type: 'file',
+ },
+ ],
+ },
+ ],
+ },
+ ],
+ }
+
+ const node = new FileNode(data)
+
+ console.log(node.toString())
+ expect(node.children.length).toBe(2)
+ expect(node.children[1].children.length).toBe(1)
+ expect(node.children[1].children[0].children.length).toBe(1)
+})
diff --git a/packages/file-graph/index.js b/packages/file-graph/index.js
new file mode 100644
index 0000000..4b1fcda
--- /dev/null
+++ b/packages/file-graph/index.js
@@ -0,0 +1,3 @@
+export { default as File } from './file.js'
+export { default as FileNode } from './fileNode.js'
+export { default as FileGraph } from './fileGraph.js'
diff --git a/packages/file-graph/package.json b/packages/file-graph/package.json
index 69b2535..2388c78 100644
--- a/packages/file-graph/package.json
+++ b/packages/file-graph/package.json
@@ -1,10 +1,9 @@
{
- "name": "file-graph",
+ "name": "@linux-odyssey/file-graph",
"version": "1.0.0",
"main": "index.js",
+ "exports": "./index.js",
"license": "MIT",
"type": "module",
- "scripts": {
- "test": "yarn node --experimental-vm-modules $(yarn bin jest)"
- }
+ "scripts": {}
}
diff --git a/server/Dockerfile b/server/Dockerfile
index 6ee9ddd..078876b 100644
--- a/server/Dockerfile
+++ b/server/Dockerfile
@@ -1,13 +1,17 @@
FROM node
WORKDIR /app
-COPY server/package.json yarn.lock ./
+COPY ./package.json yarn.lock ./
+COPY server/package.json ./server/package.json
+COPY packages/file-graph/package.json ./packages/file-graph/package.json
RUN yarn install --frozen-lockfile --production
-COPY server/src ./src
-COPY server/swagger.yaml .
+COPY ./server ./server
+COPY packages/file-graph ./packages/file-graph
COPY quests ./quests
+WORKDIR /app/server
+
ENV NODE_ENV=production
ENV HOST=0.0.0.0
ENV PORT=3000
diff --git a/server/Dockerfile.dev b/server/Dockerfile.dev
index 86186c2..738d4ea 100644
--- a/server/Dockerfile.dev
+++ b/server/Dockerfile.dev
@@ -1,7 +1,9 @@
FROM node
WORKDIR /app
-COPY server/package.json yarn.lock ./
+COPY package.json yarn.lock ./
+COPY server/package.json ./server/package.json
+COPY packages/file-graph/package.json ./packages/file-graph/package.json
RUN yarn install --frozen-lockfile
ENV NODE_ENV=development
@@ -9,4 +11,4 @@ ENV HOST=0.0.0.0
ENV PORT=3000
EXPOSE 3000
-CMD [ "yarn", "dev" ]
\ No newline at end of file
+CMD [ "yarn", "server" ]
\ No newline at end of file
diff --git a/server/package.json b/server/package.json
index 0fe6b1f..678672b 100644
--- a/server/package.json
+++ b/server/package.json
@@ -10,6 +10,7 @@
"clean": "node src/cli/clean.js"
},
"dependencies": {
+ "@linux-odyssey/file-graph": "*",
"commander": "^11.0.0",
"dockerode": "^3.3.5",
"express": "^4.18.2",
diff --git a/server/src/api/controllers/commandController.js b/server/src/api/controllers/commandController.js
index 5526308..acfdeb2 100644
--- a/server/src/api/controllers/commandController.js
+++ b/server/src/api/controllers/commandController.js
@@ -2,7 +2,7 @@
import Command from '../../models/command.js'
import Session from '../../models/session.js'
import { verifySessionJWT } from '../../utils/auth.js'
-import commandHandler from '../../game/commandHandler.js'
+import CommandHandler from '../../game/commandHandler.js'
export async function newCommand(req, res) {
console.log('new command:', req.body)
@@ -48,7 +48,9 @@ export async function newCommand(req, res) {
await c.save()
- const response = await commandHandler(session, c, additionalData)
+ const commandHandler = new CommandHandler(session, c, additionalData)
+
+ const response = await commandHandler.run()
console.log(response)
res.status(201).json(response)
diff --git a/server/src/game/commandHandler.js b/server/src/game/commandHandler.js
index 35335f3..62eb626 100644
--- a/server/src/game/commandHandler.js
+++ b/server/src/game/commandHandler.js
@@ -1,4 +1,5 @@
import minimist from 'minimist'
+import { FileGraph } from '@linux-odyssey/file-graph'
import Quest from '../models/quest.js'
import { pushToSession } from '../api/socket.js'
@@ -11,48 +12,89 @@ const checkMatch = (pattern, input) => {
})
}
-async function commandHandler(session, commandInput, additionalData) {
- const quest = await Quest.findById(session.quest)
- const stage = quest.stages.find((s) => s.id === session.progress)
- if (!stage) {
- console.error('stage not found', session.progress)
- return {}
+export default class CommandHandler {
+ constructor(session, commandInput, additionalData) {
+ this.session = session
+ this.commandInput = commandInput
+ this.argv = minimist(this.commandInput.command.split(' '))
+
+ this.additionalData = additionalData
}
- const argv = minimist(commandInput.command.split(' '))
-
- if ('discover' in additionalData) {
- pushToSession(session.id, 'graph', {
- pwd: commandInput.pwd,
- discover: additionalData.discover,
- })
- } else if (argv._[0] === 'cd') {
- pushToSession(session.id, 'graph', { pwd: commandInput.pwd })
+ handleEvent() {
+ if (this.additionalData.discover) this.discoverHandler()
}
- const commandMatch = checkMatch(stage.condition.command, commandInput.command)
- const outputMatch = checkMatch(stage.condition.output, commandInput.output)
- const errorMatch = checkMatch(stage.condition.error, commandInput.output)
+ handleCommand() {
+ const command = this.argv._[0]
+ switch (command) {
+ case 'cd':
+ pushToSession(this.session.id, 'graph', {
+ pwd: this.commandInput.pwd,
+ })
+ break
- if (!commandMatch || !outputMatch || !errorMatch) return {}
+ case 'ls':
+ pushToSession(this.session.id, 'graph', {
+ pwd: this.commandInput.pwd,
+ discover: this.additionalData.discover,
+ })
+ break
- session.completion.push(session.progress)
- session.hints.push(...stage.hints)
-
- session.progress = stage.next
- await session.save()
-
- if (stage.next === 'END') {
- session.status = 'finished'
- session.finishedAt = new Date()
- await session.save()
+ default:
+ break
+ }
}
- return {
- responses: stage.responses,
- hints: stage.hints,
- end: stage.next === 'END',
+ async discoverHandler() {
+ const graph = new FileGraph(this.session.graph)
+ graph.discover(this.additionalData.discover)
+ this.session.graph = graph
+ await this.session.save()
+ }
+
+ async run() {
+ const quest = await Quest.findById(this.session.quest)
+ const stage = quest.stages.find((s) => s.id === this.session.progress)
+ if (!stage) {
+ console.error('stage not found', this.session.progress)
+ return {}
+ }
+
+ this.handleEvent()
+ this.handleCommand()
+
+ 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
+ )
+
+ if (!commandMatch || !outputMatch || !errorMatch) return {}
+
+ this.session.completion.push(this.session.progress)
+ this.session.hints.push(...stage.hints)
+
+ this.session.progress = stage.next
+ await this.session.save()
+
+ if (stage.next === 'END') {
+ this.session.status = 'finished'
+ this.session.finishedAt = new Date()
+ await this.session.save()
+ }
+
+ return {
+ responses: stage.responses,
+ hints: stage.hints,
+ end: stage.next === 'END',
+ }
}
}
-
-export default commandHandler
diff --git a/server/src/models/session.js b/server/src/models/session.js
index 1ae266c..27d7efa 100644
--- a/server/src/models/session.js
+++ b/server/src/models/session.js
@@ -1,4 +1,18 @@
import { model, Schema } from 'mongoose'
+import { FileNode } from '@linux-odyssey/file-graph'
+
+const nodeSchema = new Schema({
+ path: String,
+ type: String,
+ discovered: {
+ type: Boolean,
+ default: false,
+ },
+})
+
+nodeSchema.add({
+ children: [nodeSchema],
+})
const Session = model(
'Session',
@@ -28,6 +42,13 @@ const Session = model(
progress: String,
completion: [String],
hints: [String],
+ graph: {
+ type: nodeSchema,
+ default: new FileNode({
+ path: '/',
+ type: 'folder',
+ }),
+ },
},
{ timestamps: true }
)
diff --git a/server/src/utils/quest.js b/server/src/utils/quest.js
index e8a2276..315528f 100644
--- a/server/src/utils/quest.js
+++ b/server/src/utils/quest.js
@@ -4,7 +4,7 @@ import yaml from 'yaml'
import Quest from '../models/quest.js'
-const questDirectory = path.join(process.cwd(), 'quests')
+const questDirectory = path.join(process.cwd(), '..', 'quests')
export default async function loadAndUpdateQuests() {
const questNames = await fs.readdir(questDirectory)
diff --git a/server/tests/api/sessionController.test.js b/server/tests/api/sessionController.test.js
deleted file mode 100644
index 991647b..0000000
--- a/server/tests/api/sessionController.test.js
+++ /dev/null
@@ -1,56 +0,0 @@
-import jest from 'jest'
-import * as sessionController from '../../src/api/controllers/sessionController'
-import Session from '../../src/models/session'
-
-jest.mock('../../src/models/session')
-
-describe('Session Controller', () => {
- let mockReq
- let mockRes
-
- beforeEach(() => {
- mockReq = {
- user: { id: 'userid' },
- body: {},
- params: {},
- }
- mockRes = {
- status: jest.fn().mockReturnThis(),
- json: jest.fn().mockReturnThis(),
- end: jest.fn().mockReturnThis(),
- }
- })
-
- test('getSessionList returns a list of sessions', async () => {
- Session.find.mockResolvedValue(['session1', 'session2'])
- await sessionController.getSessionList(mockReq, mockRes)
- expect(mockRes.json).toHaveBeenCalledWith(['session1', 'session2'])
- })
-
- test('createSession creates a new session', async () => {
- mockReq.body.exercise_id = 'exerciseid'
- const mockSession = { user: 'userid', exercise: 'exerciseid' }
- Session.mockReturnValue(mockSession)
- mockSession.save = jest.fn().mockResolvedValue('newsess')
- await sessionController.createSession(mockReq, mockRes)
- expect(mockRes.status).toHaveBeenCalledWith(201)
- expect(mockRes.json).toHaveBeenCalledWith('newsess')
- })
-
- test('getSessionById returns the correct session', async () => {
- mockReq.params.session_id = 'sessionid'
- const mockSession = { user: 'userid', _id: 'sessionid' }
- Session.findOne.mockResolvedValue(mockSession)
- await sessionController.getSessionById(mockReq, mockRes)
- expect(mockRes.json).toHaveBeenCalledWith(mockSession)
- })
-
- test('deleteSessionById deletes a session', async () => {
- mockReq.params.session_id = 'sessionid'
- const mockSession = { user: 'userid', _id: 'sessionid' }
- Session.findOneAndDelete.mockResolvedValue(mockSession)
- await sessionController.deleteSessionById(mockReq, mockRes)
- expect(mockRes.status).toHaveBeenCalledWith(204)
- expect(mockRes.end).toHaveBeenCalled()
- })
-})