From a71411d5bfe8aecbab3056c41f707ff760a1ddfa Mon Sep 17 00:00:00 2001 From: chreamy Date: Tue, 5 Mar 2024 00:26:22 -0600 Subject: [PATCH] Feat: User Session History --- packages/analytics/package.json | 2 +- packages/analytics/src/config.ts | 4 +- packages/analytics/src/controllers/users.ts | 13 +++++- packages/analytics/src/models/sessions.ts | 5 +-- packages/analytics/src/models/users.ts | 45 +++++++++++++++++-- packages/analytics/src/router.ts | 6 ++- packages/analytics/views/users.handlebars | 2 +- .../analytics/views/usersDetail.handlebars | 21 +++++++++ server/src/containers/docker.ts | 13 +++--- 9 files changed, 92 insertions(+), 19 deletions(-) create mode 100644 packages/analytics/views/usersDetail.handlebars diff --git a/packages/analytics/package.json b/packages/analytics/package.json index cb7c0f5..85c1389 100644 --- a/packages/analytics/package.json +++ b/packages/analytics/package.json @@ -9,7 +9,7 @@ "start": "node --loader ts-node/esm src/index.ts", "build": "tsc", "watch": "tsc -w", - "dev": "nodemon --loader ts-node/esm src/index.ts" + "dev": "nodemon --loader ts-node/esm src/index.ts --trace-warnings" }, "dependencies": { "@linux-odyssey/models": "*", diff --git a/packages/analytics/src/config.ts b/packages/analytics/src/config.ts index 74a6968..bece519 100644 --- a/packages/analytics/src/config.ts +++ b/packages/analytics/src/config.ts @@ -18,8 +18,8 @@ const config: Config = { host: process.env.HOST || 'localhost', port: process.env.PORT ? parseInt(process.env.PORT) : 3001, db: process.env.MONGO_URL || 'mongodb://localhost:27017/odyssey-test', - username: getOrFailed('ADMIN_USERNAME'), - password: getOrFailed('ADMIN_PASSWORD'), + username: 'a' || getOrFailed('ADMIN_USERNAME'), + password: 'a' || getOrFailed('ADMIN_PASSWORD'), baseUrl: process.env.BASE_URL || `http://${process.env.HOST || 'localhost'}:${process.env.PORT ? parseInt(process.env.PORT) : 3001}`, diff --git a/packages/analytics/src/controllers/users.ts b/packages/analytics/src/controllers/users.ts index 21c99af..a385385 100644 --- a/packages/analytics/src/controllers/users.ts +++ b/packages/analytics/src/controllers/users.ts @@ -1,5 +1,5 @@ import Pagination from '../models/pagination.ts' -import { userList } from '../models/users.js' +import { userList, userDetail, idToUser } from '../models/users.js' export async function userListController(req: any, res: any) { const { nextKey, order } = req.query @@ -20,3 +20,14 @@ export async function userListController(req: any, res: any) { res.status(500).send('Error fetching user data') } } +export async function userDetailController(req: any, res: any) { + const { id } = req.params + const sessions = await userDetail(id) + try { + const user = await idToUser(id) + res.render('usersDetail', { sessions, user }) + } catch (error) { + console.error(error) + res.status(500).send('Error fetching user data') + } +} diff --git a/packages/analytics/src/models/sessions.ts b/packages/analytics/src/models/sessions.ts index 66b5bcb..ab9cd1d 100644 --- a/packages/analytics/src/models/sessions.ts +++ b/packages/analytics/src/models/sessions.ts @@ -44,7 +44,6 @@ export async function sessionList(pagination: Pagination): Promise { commands, lastActivityAt, } = session - console.log(finishedAt) return { _id, user: user.username, // Assuming `user` is always populated. @@ -93,8 +92,8 @@ export async function sessionDetail(id: string): Promise { user: user.username, quest, status, - createdAt: createdAt.toLocaleString(), - finishedAt: finishedAt.toLocaleString(), + createdAt: createdAt?.toLocaleString(), + finishedAt: finishedAt?.toLocaleString(), usedTime: finishedAt ? formatTime(finishedAt.getTime() - createdAt.getTime()) : '', diff --git a/packages/analytics/src/models/users.ts b/packages/analytics/src/models/users.ts index 92dff29..fa7b16b 100644 --- a/packages/analytics/src/models/users.ts +++ b/packages/analytics/src/models/users.ts @@ -1,13 +1,27 @@ import { Session, User } from '@linux-odyssey/models' import Pagination from './pagination.ts' +import { ObjectId, Model } from 'mongoose' -function loginMethods(user: any) { - const methods = [] +interface IUser extends Document { + hashedPassword?: string + socialLogins?: Record +} + +function loginMethods(user: IUser): string[] { + const methods: string[] = [] if (user.hashedPassword) methods.push('password') - if (user.socialLogins) methods.push(...Object.keys(user.socialLogins)) + if (user.socialLogins && typeof user.socialLogins === 'object') { + methods.push(...Object.keys(user.socialLogins)) + } return methods } +function formatTime(time: number) { + const seconds = Math.floor(time / 1000) + const minutes = Math.floor(seconds / 60) + const hours = Math.floor(minutes / 60) + return `${hours}h ${minutes % 60}m ${seconds % 60}s` +} export function userCount() { return User.countDocuments({}) } @@ -48,3 +62,28 @@ export async function userList(pagination: Pagination) { } }) } +export async function userDetail(id: ObjectId): Promise { + const sessions = await Session.find({ + user: id, + }) + return sessions.map((session) => { + const { _id, user, quest, status, createdAt, lastActivityAt } = session + return { + _id, + quest, + status, + createdAt: createdAt?.toLocaleString(), + lastActivityAt: lastActivityAt?.toLocaleString(), + usedTime: + lastActivityAt && createdAt + ? formatTime(lastActivityAt.getTime() - createdAt.getTime()) + : 'N/A', // Ensure both dates are present + } + }) +} +export async function idToUser(id: ObjectId): Promise { + const user = await User.findOne({ + _id: id, + }) + return user?.username || 'User not found' +} diff --git a/packages/analytics/src/router.ts b/packages/analytics/src/router.ts index 8082768..a68841d 100644 --- a/packages/analytics/src/router.ts +++ b/packages/analytics/src/router.ts @@ -1,7 +1,10 @@ import { Router } from 'express' import { homeController } from './controllers/index.js' -import { userListController } from './controllers/users.js' +import { + userListController, + userDetailController, +} from './controllers/users.js' import { questListController } from './controllers/quests.ts' import { commandListController } from './controllers/commands.ts' import { @@ -13,6 +16,7 @@ const router = Router() router.get('/', homeController) router.get('/users', userListController) +router.get('/users/:id', userDetailController) router.get('/quests', questListController) router.get('/commands', commandListController) router.get('/sessions', sessionListController) diff --git a/packages/analytics/views/users.handlebars b/packages/analytics/views/users.handlebars index dde1654..d3312ef 100644 --- a/packages/analytics/views/users.handlebars +++ b/packages/analytics/views/users.handlebars @@ -15,7 +15,7 @@ {{#each users}} - {{username}} + {{username}} {{lastActivityAt}} {{sessionCount}} {{email}} diff --git a/packages/analytics/views/usersDetail.handlebars b/packages/analytics/views/usersDetail.handlebars new file mode 100644 index 0000000..3e1f643 --- /dev/null +++ b/packages/analytics/views/usersDetail.handlebars @@ -0,0 +1,21 @@ +

{{user}}'s Sessions History

+
+ + + + + + + + + {{#each sessions}} + + + + + + + + {{/each}} +
QuestStatusCreated AtUsed TimeSession
{{quest}}{{status}}{{createdAt}}{{usedTime}}Detail
+
\ No newline at end of file diff --git a/server/src/containers/docker.ts b/server/src/containers/docker.ts index 8e7b219..8ac2d03 100644 --- a/server/src/containers/docker.ts +++ b/server/src/containers/docker.ts @@ -16,10 +16,9 @@ const containerOptions = { }, } as Docker.ContainerCreateOptions -export function createContainer( - name: string, - questId: string -): Promise { +// const network = engine.getNetwork(config.dockerNetwork) + +export function createContainer(name, questId) { const option = { ...containerOptions, name, @@ -28,9 +27,9 @@ export function createContainer( const { hostPwd, mountQuest } = config.docker if (!config.isProduction && hostPwd && mountQuest === questId) { logger.info('Mounting quest folder', mountQuest) - option.HostConfig!.Binds = [ - `${hostPwd}/quests/${mountQuest}/home:/home/commander`, - `${hostPwd}/packages/container:/usr/local/lib/container`, + option.HostConfig.Binds = [ + `${hostPwd.replace(/\\/g, '/')}/quests/${mountQuest}/home:/home/commander`, + `${hostPwd.replace(/\\/g, '/')}/packages/container:/usr/local/lib/container`, ] } return engine.createContainer(option)