diff --git a/.eslintrc.js b/.eslintrc.js index e36019a..ebfa30a 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -19,5 +19,6 @@ module.exports = { 'no-console': process.env.NODE_ENV === 'production' ? 'warn' : 'off', 'no-debugger': process.env.NODE_ENV === 'production' ? 'warn' : 'off', 'import/extensions': 'off', + camelcase: ['error', { properties: 'never', ignoreDestructuring: true }], }, } diff --git a/README.md b/README.md index 21d4c9e..2b70dd9 100644 --- a/README.md +++ b/README.md @@ -40,8 +40,22 @@ yarn app ## CLI terminal client -Open server first, then +Open server first, then create a session by `POST /api/v1/sessions` + +```bash +curl -X 'POST' \ + 'http://localhost:3000/api/v1/sessions' \ + -H 'accept: application/json' \ + -H 'Content-Type: application/json' \ + -d '{ + "quest_id": "helloworld" +}' +``` + +Then use the `_id` field in response ``` -yarn cli +yarn cli [_id] +# example +yarn cli 64be226a39c0043cd1cdf7c2 ``` diff --git a/server/src/api/controllers/sessionController.js b/server/src/api/controllers/sessionController.js index 574e50d..19d431d 100644 --- a/server/src/api/controllers/sessionController.js +++ b/server/src/api/controllers/sessionController.js @@ -25,6 +25,7 @@ export async function createSession(req, res) { const newSession = new Session({ user: req.user, quest, + containerId: `quest-${quest.id}-${req.user.username}-${Date.now()}`, }) try { diff --git a/server/src/api/socket.js b/server/src/api/socket.js index d8c1379..5f08973 100644 --- a/server/src/api/socket.js +++ b/server/src/api/socket.js @@ -1,12 +1,38 @@ import { Server } from 'socket.io' import { getOrCreateContainer, attachContainer } from '../utils/docker.js' +import Session from '../models/session.js' +import { defaultUser } from '../utils/auth.js' export default (server) => { const io = new Server(server) + io.use(async (socket, next) => { + console.log('Authenticating...') + try { + const user = await defaultUser() + // eslint-disable-next-line no-param-reassign + socket.user = user + next() + } catch (err) { + console.error(err) + next(err) + } + }) + io.on('connection', async (socket) => { console.log('Connected to the client.') - const container = await getOrCreateContainer('odyssey-test') + const session = await Session.findOne({ + _id: socket.handshake.query.session_id, + user: socket.user, + }) + + if (!session) { + socket.send('Session not found.') + socket.disconnect() + return + } + + const container = await getOrCreateContainer(session.containerId) const stream = await attachContainer(container) stream.on('data', (chunk) => { @@ -22,6 +48,6 @@ export default (server) => { console.log('Disconnected from the client.') }) - socket.send('Hello from server!') + socket.send(`${container.id}\n`) }) } diff --git a/server/src/client.js b/server/src/client.js index cdbf7b8..40e16cd 100644 --- a/server/src/client.js +++ b/server/src/client.js @@ -5,7 +5,14 @@ stdin.setRawMode(true) stdin.resume() stdin.setEncoding('utf8') -const socket = io('ws://localhost:3000') +const sessionId = process.argv[2] +console.log(`Session ID: ${sessionId}`) + +const socket = io('ws://localhost:3000', { + query: { + session_id: sessionId, + }, +}) socket.on('open', function open() { console.log('Connected to the server.') diff --git a/server/src/middleware/auth.js b/server/src/middleware/auth.js index 43c8826..62bb190 100644 --- a/server/src/middleware/auth.js +++ b/server/src/middleware/auth.js @@ -1,17 +1,9 @@ /* eslint-disable import/prefer-default-export */ -import User from '../models/user.js' +import { defaultUser } from '../utils/auth.js' export async function authMiddleware(req, res, next) { try { - let user = await User.findOne({ username: 'defaultUser' }) - if (!user) { - user = new User({ - username: 'defaultUser', - email: 'john@example.com', - }) - await user.save() - } - req.user = user + req.user = await defaultUser() next() } catch (err) { res.status(500).json({ message: err.message }) diff --git a/server/src/utils/auth.js b/server/src/utils/auth.js new file mode 100644 index 0000000..e33cf5f --- /dev/null +++ b/server/src/utils/auth.js @@ -0,0 +1,14 @@ +/* eslint-disable import/prefer-default-export */ +import User from '../models/user.js' + +export async function defaultUser() { + let user = await User.findOne({ username: 'defaultUser' }) + if (!user) { + user = new User({ + username: 'defaultUser', + email: 'jackson@example.com', + }) + await user.save() + } + return user +} diff --git a/server/src/utils/docker.js b/server/src/utils/docker.js index 1095d38..a78b738 100644 --- a/server/src/utils/docker.js +++ b/server/src/utils/docker.js @@ -13,9 +13,24 @@ const containerOptions = { StdinOnce: false, } -export async function getOrCreateContainer(name) { +export async function createContainer() { + const container = await engine.createContainer({ + ...containerOptions, + }) + await container.start() + return container +} + +export async function getContainer(id) { + const container = await engine.getContainer(id) + await container.start() + return container +} + +export async function getOrCreateContainer(id) { + console.log(`Getting container: ${id}`) try { - const container = engine.getContainer(name) + const container = engine.getContainer(id) if (!(await container.inspect()).State.Running) { await container.start() } @@ -23,7 +38,7 @@ export async function getOrCreateContainer(name) { } catch (error) { const container = await engine.createContainer({ ...containerOptions, - name, + name: id, }) await container.start() console.log(container.id) @@ -31,12 +46,20 @@ export async function getOrCreateContainer(name) { } } -export function attachContainer(container) { +export async function attachContainer(container) { // Create an exec instance with bash shell - return container.attach({ - stream: true, - stdin: true, - stdout: true, - stderr: true, + const exec = await container.exec({ + AttachStdin: true, + AttachStdout: true, + AttachStderr: true, + Cmd: ['/bin/bash'], + Tty: true, }) + + const execOutput = await exec.start({ + Detach: false, + Tty: true, + }) + + return execOutput.socket }