diff --git a/package.json b/package.json index b028471..e1cac1e 100644 --- a/package.json +++ b/package.json @@ -15,7 +15,7 @@ "app": "yarn workspace app dev", "app:local": "yarn workspace app dev:local", "build": "yarn workspaces foreach -Apt run build", - "dev": "yarn workspaces foreach -Api --include '{server,app}' run watch", + "dev": "yarn workspaces foreach -Api --include '{server,app}' run dev", "server": "yarn workspace server dev", "cli": "yarn workspace client start", "lint": "eslint .", diff --git a/packages/models/src/index.ts b/packages/models/src/index.ts index aa34aad..a043ec3 100644 --- a/packages/models/src/index.ts +++ b/packages/models/src/index.ts @@ -1,10 +1,10 @@ import mongoose from 'mongoose' -export { User } from './user.js' +export { User, IUser } from './user.js' export { Quest, IQuest } from './quest.js' -export { Session } from './session.js' +export { Session, ISession } from './session.js' export { Command, ICommand } from './command.js' -export { UserProfile } from './userProfile.js' +export { UserProfile, IUserProfile } from './userProfile.js' export default async function connectDB(uri: string) { const db = await mongoose.connect(uri) diff --git a/packages/models/src/session.ts b/packages/models/src/session.ts index ab3ba45..f7926be 100644 --- a/packages/models/src/session.ts +++ b/packages/models/src/session.ts @@ -44,7 +44,7 @@ const taskSchema = new Schema({ export interface ISession { user: Types.ObjectId quest: string - containerId: string + containerId: string | null status: string createdAt: Date updatedAt: Date diff --git a/server/package.json b/server/package.json index 846bd73..edc3559 100644 --- a/server/package.json +++ b/server/package.json @@ -45,9 +45,11 @@ }, "devDependencies": { "@types/bcrypt": "^5.0.2", + "@types/dockerode": "^3.3.23", "@types/express": "^4.17.21", "@types/express-session": "^1.17.10", "@types/jsonwebtoken": "^9.0.5", + "@types/node-schedule": "^2.1.6", "@types/passport": "^1.0.16", "@types/passport-github2": "^1.2.9", "@types/passport-jwt": "^4.0.1", diff --git a/server/src/containers/docker.js b/server/src/containers/docker.ts similarity index 79% rename from server/src/containers/docker.js rename to server/src/containers/docker.ts index 17fc9ec..a29fd85 100644 --- a/server/src/containers/docker.js +++ b/server/src/containers/docker.ts @@ -14,9 +14,12 @@ const containerOptions = { HostConfig: { NetworkMode: config.docker.network, }, -} +} as Docker.ContainerCreateOptions -export function createContainer(name, questId) { +export function createContainer( + name: string, + questId: string +): Promise { const option = { ...containerOptions, name, @@ -25,7 +28,7 @@ export function createContainer(name, questId) { const { hostPwd, mountQuest } = config.docker if (!config.isProduction && hostPwd && mountQuest === questId) { logger.info('Mounting quest folder', mountQuest) - option.HostConfig.Binds = [ + option.HostConfig!.Binds = [ `${hostPwd}/quests/${mountQuest}/home:/home/commander`, `${hostPwd}/packages/container:/usr/local/lib/container`, ] @@ -33,7 +36,9 @@ export function createContainer(name, questId) { return engine.createContainer(option) } -export async function getAndStartContainer(id) { +export async function getAndStartContainer( + id: string +): Promise { logger.debug(`Getting container: ${id}`) const container = engine.getContainer(id) if (!container) { @@ -45,7 +50,10 @@ export async function getAndStartContainer(id) { return container } -export async function attachContainer(container, { token }) { +export async function attachContainer( + container: Docker.Container, + { token }: { token: string } +) { const exec = await container.exec({ AttachStdin: true, AttachStdout: true, @@ -67,7 +75,7 @@ export async function attachContainer(container, { token }) { return execOutput } -export async function deleteContainer(id) { +export async function deleteContainer(id: string) { const container = engine.getContainer(id) if (!container) { return @@ -80,8 +88,8 @@ export async function deleteContainer(id) { await container.remove() } -function parseJSONOutput(data) { - const { stream, error } = JSON.parse(data.toString()) +function parseJSONOutput(data: string) { + const { stream, error } = JSON.parse(data) if (stream) { // eslint-disable-next-line no-console console.log(stream) @@ -91,7 +99,7 @@ function parseJSONOutput(data) { } } -export function buildQuestImage(questPath, questId) { +export function buildQuestImage(questPath: string, questId: string) { return new Promise((resolve, reject) => { engine.buildImage( { @@ -114,9 +122,9 @@ export function buildQuestImage(questPath, questId) { data .toString() .split('\n') - .map((line) => line.trim()) - .filter((line) => line !== '') - .forEach((line) => { + .map((line: string) => line.trim()) + .filter((line: string) => line !== '') + .forEach((line: string) => { parseJSONOutput(line) }) } catch (error) { diff --git a/server/src/containers/expiryChecker.js b/server/src/containers/expiryChecker.ts similarity index 61% rename from server/src/containers/expiryChecker.js rename to server/src/containers/expiryChecker.ts index 764b619..20a1007 100644 --- a/server/src/containers/expiryChecker.js +++ b/server/src/containers/expiryChecker.ts @@ -1,18 +1,30 @@ import schedule from 'node-schedule' import { Session } from '@linux-odyssey/models' +import type { ISession } from '@linux-odyssey/models' +import type { HydratedDocument } from 'mongoose' import config from '../config.js' import { deleteContainer } from './docker.js' import logger from '../utils/logger.js' -async function disableSession(session) { +async function disableSession(session: HydratedDocument) { + if (!session.containerId) { + logger.warn(`Deleting session ${session._id} has no containerId`) + return + } try { await deleteContainer(session.containerId) } catch (err) { - // If the container doesn't exist, we can ignore the error - if (err.statusCode !== 404) { - throw new Error( - `Failed to delete container ${session.containerId} for session ${session._id}` - ) + // Perform runtime check to safely access statusCode + if (typeof err === 'object' && err !== null && 'statusCode' in err) { + const error = err as { statusCode: number } + if (error.statusCode !== 404) { + throw new Error( + `Failed to delete container ${session.containerId} for session ${session._id}` + ) + } + } else { + // Handle the case where err is not the expected type + logger.error(`An unexpected error occurred: ${err}`) } } if (session.status === 'active') { diff --git a/yarn.lock b/yarn.lock index 6376beb..42fd647 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1110,10 +1110,19 @@ __metadata: languageName: unknown linkType: soft -"@linux-odyssey/models@npm:*, @linux-odyssey/models@workspace:packages/models": +"@linux-odyssey/game@workspace:packages/game": + version: 0.0.0-use.local + resolution: "@linux-odyssey/game@workspace:packages/game" + dependencies: + "@linux-odyssey/models": "workspace:^" + languageName: unknown + linkType: soft + +"@linux-odyssey/models@npm:*, @linux-odyssey/models@workspace:^, @linux-odyssey/models@workspace:packages/models": version: 0.0.0-use.local resolution: "@linux-odyssey/models@workspace:packages/models" dependencies: + "@types/mongoose": "npm:^5.11.97" mongoose: "npm:^8.0.3" languageName: unknown linkType: soft @@ -1395,6 +1404,26 @@ __metadata: languageName: node linkType: hard +"@types/docker-modem@npm:*": + version: 3.0.6 + resolution: "@types/docker-modem@npm:3.0.6" + dependencies: + "@types/node": "npm:*" + "@types/ssh2": "npm:*" + checksum: 10c0/d3ffd273148bc883ff9b1a972b1f84c1add6d9a197d2f4fc9774db4c814f39c2e51cc649385b55d781c790c16fb0bf9c1f4c62499bd0f372a4b920190919445d + languageName: node + linkType: hard + +"@types/dockerode@npm:^3.3.23": + version: 3.3.23 + resolution: "@types/dockerode@npm:3.3.23" + dependencies: + "@types/docker-modem": "npm:*" + "@types/node": "npm:*" + checksum: 10c0/b6bdb2851487ac7168d2a790ef35b50f0521cbf01e636feaca8f7192fe9c4c687c22f71c56767f41b25ceb87d60ebbc052c1e28c6f7d6cc7a69327050569c863 + languageName: node + linkType: hard + "@types/express-serve-static-core@npm:^4.17.33": version: 4.17.43 resolution: "@types/express-serve-static-core@npm:4.17.43" @@ -1516,6 +1545,24 @@ __metadata: languageName: node linkType: hard +"@types/mongoose@npm:^5.11.97": + version: 5.11.97 + resolution: "@types/mongoose@npm:5.11.97" + dependencies: + mongoose: "npm:*" + checksum: 10c0/333e692a39734948db700c76f25c7b088f46cb88ba940b80467313acf29752823ff9ed2870045c34e00559c38a4a3b5136ae6634da6dbe56c160a16a50e1dea0 + languageName: node + linkType: hard + +"@types/node-schedule@npm:^2.1.6": + version: 2.1.6 + resolution: "@types/node-schedule@npm:2.1.6" + dependencies: + "@types/node": "npm:*" + checksum: 10c0/5b8f810abf1738f4114c771434ebd17b6ec4f5e29d9f4ca4085b7b5d016f88dd6d99c7fa5c9e0d8583a52781e7ab579f34adfd6927071aa03168b44d9d75e7fd + languageName: node + linkType: hard + "@types/node@npm:*, @types/node@npm:>=10.0.0, @types/node@npm:^20.10.4": version: 20.11.16 resolution: "@types/node@npm:20.11.16" @@ -1525,6 +1572,15 @@ __metadata: languageName: node linkType: hard +"@types/node@npm:^18.11.18": + version: 18.19.17 + resolution: "@types/node@npm:18.19.17" + dependencies: + undici-types: "npm:~5.26.4" + checksum: 10c0/ac2e4910d5f2bb92cb2623f6d4d4d665a39cf1d425993347e6c3756c70a4def21a47241b86f49e1e4f441529753527bbd9be5f08f29fdac38c64035a26b847c7 + languageName: node + linkType: hard + "@types/oauth@npm:*": version: 0.9.4 resolution: "@types/oauth@npm:0.9.4" @@ -1652,6 +1708,15 @@ __metadata: languageName: node linkType: hard +"@types/ssh2@npm:*": + version: 1.11.19 + resolution: "@types/ssh2@npm:1.11.19" + dependencies: + "@types/node": "npm:^18.11.18" + checksum: 10c0/d1a6f8bff3602ea40be0f9b41f2c80bdd8debbda412b50cda5d7b973ef3921ede60123ea0c48c9e202d96d71c7dc05c9019b5736e8b2ddc6c457c557f602453e + languageName: node + linkType: hard + "@types/stack-utils@npm:^2.0.0": version: 2.0.3 resolution: "@types/stack-utils@npm:2.0.3" @@ -7178,6 +7243,21 @@ __metadata: languageName: node linkType: hard +"mongoose@npm:*": + version: 8.1.3 + resolution: "mongoose@npm:8.1.3" + dependencies: + bson: "npm:^6.2.0" + kareem: "npm:2.5.1" + mongodb: "npm:6.3.0" + mpath: "npm:0.9.0" + mquery: "npm:5.0.0" + ms: "npm:2.1.3" + sift: "npm:16.0.1" + checksum: 10c0/68cf62251b245fb3f9867f0f66895b751f688dcda4c3b9b9016754ad6d78428535d7a4094f013e6e4545158ed2d766aea94aff4478b7a2e3876cefe79a59447c + languageName: node + linkType: hard + "mongoose@npm:^8.0.3": version: 8.1.1 resolution: "mongoose@npm:8.1.1" @@ -8530,9 +8610,11 @@ __metadata: "@linux-odyssey/models": "npm:*" "@linux-odyssey/utils": "npm:*" "@types/bcrypt": "npm:^5.0.2" + "@types/dockerode": "npm:^3.3.23" "@types/express": "npm:^4.17.21" "@types/express-session": "npm:^1.17.10" "@types/jsonwebtoken": "npm:^9.0.5" + "@types/node-schedule": "npm:^2.1.6" "@types/passport": "npm:^1.0.16" "@types/passport-github2": "npm:^1.2.9" "@types/passport-jwt": "npm:^4.0.1"