Analytics revise (#91)

Analytics revise
This commit is contained in:
Lin, Hung-Shin authored and GitHub committed 2023-12-21 18:13:53 +08:00
commit 4a4ce11bcc
24 files changed
+391 -148

No files matched your search

+2 -1
View File
@@ -13,7 +13,8 @@
"@linux-odyssey/models": "*", "@linux-odyssey/models": "*",
"express": "^4.18.2", "express": "^4.18.2",
"express-basic-auth": "^1.2.1", "express-basic-auth": "^1.2.1",
"express-handlebars": "^7.1.2" "express-handlebars": "^7.1.2",
"mongoose": "^8.0.3"
}, },
"devDependencies": { "devDependencies": {
"nodemon": "^3.0.1" "nodemon": "^3.0.1"
View File
Whitespace-only changes.
+18 -3
View File
@@ -1,9 +1,24 @@
/* eslint-disable object-shorthand */
import Pagination from '../models/pagination.js'
import { errorCommands } from '../models/commands.js' import { errorCommands } from '../models/commands.js'
// eslint-disable-next-line import/prefer-default-export // eslint-disable-next-line import/prefer-default-export
export async function commandListController(req, res) { export async function commandListController(req, res) {
const payload = { const { nextKey, order } = req.query
errorCommands: await errorCommands(), const itemsPerPage = 100
try {
const pagination = new Pagination(order, itemsPerPage, nextKey)
const payload = await errorCommands(pagination)
const newNextKey = payload[payload.length - 1]?._id
res.render('commands', {
errorCommands: payload,
nextKey: newNextKey,
order: pagination.getOrder(),
})
} catch (error) {
console.error(error)
res.status(500).send('Error fetching data')
} }
res.render('commands', payload)
} }
+13 -4
View File
@@ -1,15 +1,24 @@
import { userCount } from '../models/users.js' import { userCount } from '../models/users.js'
import { sessionCount, sessionCompleted } from '../models/sessions.js' import { questList } from '../models/quests.js'
// eslint-disable-next-line import/prefer-default-export // eslint-disable-next-line import/prefer-default-export
export async function homeController(req, res) { export async function homeController(req, res) {
const quests = await questList()
const [totalQuests, totalCompleted] = quests.reduce(
([total, completed], quest) => [
total + quest.started,
completed + quest.completed,
],
[0, 0]
)
const payload = { const payload = {
title: 'Linux Odyssey Admin', title: 'Linux Odyssey Admin',
userCount: await userCount(), userCount: await userCount(),
sessionCount: await sessionCount(), totalQuests,
sessionCompleted: await sessionCompleted(), totalCompleted,
} }
payload.completedRate = payload.completedRate =
Math.round((payload.sessionCompleted / payload.sessionCount) * 10000) / 100 Math.round((payload.totalCompleted / payload.totalQuests) * 10000) / 100
res.render('home', payload) res.render('home', payload)
} }
@@ -0,0 +1,10 @@
import { questList } from '../models/quests.js'
// eslint-disable-next-line import/prefer-default-export
export async function questListController(req, res) {
const quests = await questList()
quests.forEach((quest) => {
quest.failed = quest.started - quest.completed
})
res.render('quests', { quests })
}
+18 -2
View File
@@ -1,8 +1,24 @@
import Pagination from '../models/pagination.js'
import { sessionList, sessionDetail } from '../models/sessions.js' import { sessionList, sessionDetail } from '../models/sessions.js'
export async function sessionListController(req, res) { export async function sessionListController(req, res) {
const sessions = await sessionList() const { nextKey, order } = req.query
res.render('sessions', { sessions }) const itemsPerPage = 50
try {
const pagination = new Pagination(order, itemsPerPage, nextKey)
const sessions = await sessionList(pagination)
const newNextKey = sessions[sessions.length - 1]?._id
res.render('sessions', {
sessions,
nextKey: newNextKey,
order: pagination.getOrder(),
})
} catch (error) {
console.log(error)
res.status(500).send('Error fetching session data')
}
} }
export async function sessionDetailController(req, res) { export async function sessionDetailController(req, res) {
+18 -2
View File
@@ -1,6 +1,22 @@
import Pagination from '../models/pagination.js'
import { userList } from '../models/users.js' import { userList } from '../models/users.js'
// eslint-disable-next-line import/prefer-default-export
export async function userListController(req, res) { export async function userListController(req, res) {
res.render('users', { users: await userList() }) const { nextKey, order } = req.query
const itemsPerPage = 50
try {
const pagination = new Pagination(order, itemsPerPage, nextKey)
const users = await userList(pagination)
const newNextKey = users[users.length - 1]?._id
res.render('users', {
users,
nextKey: newNextKey,
order: pagination.getOrder(),
})
} catch (error) {
console.error(error)
res.status(500).send('Error fetching user data')
}
} }
+36 -14
View File
@@ -1,22 +1,44 @@
import { Command } from '@linux-odyssey/models' import { Command } from '@linux-odyssey/models'
// eslint-disable-next-line import/prefer-default-export // eslint-disable-next-line import/prefer-default-export
export async function errorCommands() { export async function errorCommands(pagination) {
const commands = await Command.find({ const commands = await Command.aggregate([
$and: [ pagination.match('_id'),
{ error: { $exists: true } }, {
{ error: { $ne: null } }, $match: {
{ error: { $ne: '' } }, $and: [
], { error: { $exists: true } },
}) { error: { $ne: null } },
.populate({ { error: { $ne: '' } },
path: 'session', ],
populate: { path: 'user' }, },
}) },
.exec() {
return commands.map(({ command, error, createdAt, session }) => { $lookup: {
from: 'sessions', // 替換成 'session' 對應的集合名稱
localField: 'session', // 替換成 'session' 對應的字段名稱
foreignField: '_id',
as: 'session',
},
},
{ $unwind: '$session' },
{
$lookup: {
from: 'users', // 替換成 'user' 對應的集合名稱
localField: 'session.user', // 替換成 'user' 對應的字段名稱
foreignField: '_id',
as: 'session.user',
},
},
{ $unwind: '$session.user' },
pagination.sort('_id'),
pagination.limit(),
])
return commands.map(({ _id, command, error, createdAt, session }) => {
const { user, quest } = session const { user, quest } = session
return { return {
_id,
command: command.slice(0, 20), command: command.slice(0, 20),
error: error.slice(0, 50), error: error.slice(0, 50),
createdAt: createdAt?.toLocaleString(), createdAt: createdAt?.toLocaleString(),
@@ -0,0 +1,74 @@
/* eslint-disable lines-between-class-members */
/* eslint-disable class-methods-use-this */
// eslint-disable-next-line max-classes-per-file
import mongoose from 'mongoose'
export const DESC = 'desc'
export const ASC = 'asc'
class Asc {
getOperator() {
return '$gt'
}
getOrder() {
return 1
}
getName() {
return ASC
}
}
class Desc {
getOperator() {
return '$lt'
}
getOrder() {
return -1
}
getName() {
return DESC
}
}
export default class Pagination {
nextKey = null
itemsPerPage = 50
order = null
constructor(order, itemsPerPage = 50, nextKey = null) {
this.itemsPerPage = itemsPerPage
if (!order || order === DESC) {
this.order = new Desc()
} else if (order === ASC) {
this.order = new Asc()
} else {
throw new Error('order must be asc or desc')
}
if (nextKey) {
this.nextKey = new mongoose.Types.ObjectId(nextKey)
}
}
match(field) {
const stage = this.nextKey
? { [field]: { [this.order.getOperator()]: this.nextKey } }
: {}
return { $match: stage }
}
sort(field) {
return { $sort: { [field]: this.order.getOrder() } }
}
limit() {
return { $limit: this.itemsPerPage }
}
getOrder() {
return this.order.getName()
}
}
+59
View File
@@ -0,0 +1,59 @@
/* eslint-disable object-shorthand */
/* eslint-disable func-names */
import { UserProfile } from '@linux-odyssey/models'
export function questList() {
return UserProfile.aggregate([
{
$project: {
progress: {
$objectToArray: '$progress',
},
},
},
{
$unwind: '$progress',
},
{
$group: {
_id: '$progress.v.quest',
started: {
$sum: 1,
},
completed: {
$sum: {
$cond: {
if: { $eq: ['$progress.v.completed', true] },
then: 1,
else: 0,
},
},
},
},
},
{
$project: {
_id: 1,
started: 1,
completed: 1,
completedRate: {
$cond: {
if: { $eq: ['$started', 0] },
then: 0,
else: {
$round: [
{ $multiply: [{ $divide: ['$completed', '$started'] }, 100] },
0,
],
},
},
},
},
},
{
$sort: {
completed: -1,
},
},
])
}
+5 -24
View File
@@ -1,28 +1,5 @@
import { Command, Session } from '@linux-odyssey/models' import { Command, Session } from '@linux-odyssey/models'
export function sessionCount() {
return Session.find().count()
}
export function sessionCompleted() {
return Session.find({ status: 'finished' }).count()
}
export function averageTimeUsage() {
return Session.aggregate([
{
$group: {
_id: '$quest',
average: {
$avg: {
$subtract: ['$finishedAt', '$createdAt'],
},
},
},
},
])
}
function formatTime(time) { function formatTime(time) {
const seconds = Math.floor(time / 1000) const seconds = Math.floor(time / 1000)
const minutes = Math.floor(seconds / 60) const minutes = Math.floor(seconds / 60)
@@ -30,8 +7,9 @@ function formatTime(time) {
return `${hours}h ${minutes % 60}m ${seconds % 60}s` return `${hours}h ${minutes % 60}m ${seconds % 60}s`
} }
export async function sessionList() { export async function sessionList(pagination) {
const sessions = await Session.aggregate([ const sessions = await Session.aggregate([
pagination.match('_id'),
{ {
$lookup: { $lookup: {
from: 'users', from: 'users',
@@ -49,7 +27,10 @@ export async function sessionList() {
as: 'commands', as: 'commands',
}, },
}, },
pagination.sort('_id'),
pagination.limit(),
]) ])
return sessions.map( return sessions.map(
({ ({
_id, _id,
+7 -3
View File
@@ -11,8 +11,9 @@ export function userCount() {
return User.find().count() return User.find().count()
} }
export async function userList() { export async function userList(pagination) {
const users = await Session.aggregate([ const users = await Session.aggregate([
pagination.match('user'),
{ {
$group: { $group: {
_id: '$user', _id: '$user',
@@ -29,11 +30,14 @@ export async function userList() {
}, },
}, },
{ $unwind: '$userData' }, { $unwind: '$userData' },
{ $sort: { 'userData.createdAt': 1 } }, pagination.sort('userData._id'),
pagination.limit(),
]) ])
return users.map(({ userData, count, lastActivityAt }) => { return users.map(({ userData, count, lastActivityAt }) => {
const { username, email, createdAt } = userData const { _id, username, email, createdAt } = userData
return { return {
_id,
username, username,
email, email,
createdAt: createdAt?.toLocaleString(), createdAt: createdAt?.toLocaleString(),
+2
View File
@@ -2,6 +2,7 @@ import { Router } from 'express'
import { homeController } from './controllers/index.js' import { homeController } from './controllers/index.js'
import { userListController } from './controllers/users.js' import { userListController } from './controllers/users.js'
import { questListController } from './controllers/quests.js'
import { commandListController } from './controllers/commands.js' import { commandListController } from './controllers/commands.js'
import { import {
sessionDetailController, sessionDetailController,
@@ -12,6 +13,7 @@ const router = Router()
router.get('/', homeController) router.get('/', homeController)
router.get('/users', userListController) router.get('/users', userListController)
router.get('/quests', questListController)
router.get('/commands', commandListController) router.get('/commands', commandListController)
router.get('/sessions', sessionListController) router.get('/sessions', sessionListController)
router.get('/sessions/:id', sessionDetailController) router.get('/sessions/:id', sessionDetailController)
@@ -0,0 +1,48 @@
import mongoose from 'mongoose'
import Pagination, { ASC, DESC } from '../src/models/pagination.js'
describe('Pagination', () => {
it('should failed when not provide order', () => {
expect(new Pagination('').getOrder()).toBe(DESC)
expect(() => new Pagination('Hahaha')).toThrow()
})
it('should return correct limit', () => {
const pagination = new Pagination(DESC, 10)
expect(pagination.limit()).toMatchObject({ $limit: 10 })
})
it('desc should return correct order', async () => {
const pagination = new Pagination(DESC, 10)
expect(pagination.sort('_id')).toMatchObject({ $sort: { _id: -1 } })
expect(pagination.sort('time')).toMatchObject({ $sort: { time: -1 } })
})
it('asc should return correct order', async () => {
const pagination = new Pagination(ASC, 10)
expect(pagination.sort('_id')).toMatchObject({ $sort: { _id: 1 } })
expect(pagination.sort('time')).toMatchObject({ $sort: { time: 1 } })
})
it('should return correct match stage for empty nextKey', async () => {
const pagination = new Pagination(DESC, 10)
expect(pagination.match('_id')).toMatchObject({ $match: {} })
})
it('should return correct match stage for empty nextKey', async () => {
const nextKeyStr = '656c7fea28a4c087bfcefe12'
const pagination = new Pagination(DESC, 10, nextKeyStr)
const nextKey = new mongoose.Types.ObjectId(nextKeyStr)
expect(pagination.match('_id')).toMatchObject({
$match: {
_id: {
$lt: nextKey,
},
},
})
})
})
@@ -1,6 +1,10 @@
<h1>Commands</h1> <h1>Commands</h1>
<div> <div>
<h2>Error commands</h2> <h2>Error commands</h2>
<div class='pagination'>
<a href='/commands?order=asc'>Ascending</a>
<a href='/commands?order=desc'>Descending</a>
</div>
<table> <table>
<tr> <tr>
<th>Command</th> <th>Command</th>
@@ -21,4 +25,7 @@
</tr> </tr>
{{/each}} {{/each}}
</table> </table>
<div class='pagination'>
<a href='/commands?nextKey={{nextKey}}&order={{order}}'>Next</a>
</div>
</div> </div>
+5 -5
View File
@@ -1,18 +1,18 @@
<h1>Welcome to {{title}}</h1> <h1>Welcome to {{title}}</h1>
<div> <div>
<h2>Users</h2> <h2>Overview</h2>
<table> <table>
<tr> <tr>
<th>Registered Users</th> <th>Registered Users</th>
<td>{{userCount}}</td> <td>{{userCount}}</td>
</tr> </tr>
<tr> <tr>
<th>Session Count</th> <th>Total Started Quests</th>
<td>{{sessionCount}}</td> <td>{{totalQuests}}</td>
</tr> </tr>
<tr> <tr>
<th>Completed Session</th> <th>Completed Quests</th>
<td>{{sessionCompleted}}</td> <td>{{totalCompleted}}</td>
</tr> </tr>
<tr> <tr>
<th>Completed Rate</th> <th>Completed Rate</th>
@@ -8,6 +8,7 @@
<ul> <ul>
<li><a href='/'>Home</a></li> <li><a href='/'>Home</a></li>
<li><a href='/users'>Users</a></li> <li><a href='/users'>Users</a></li>
<li><a href='/quests'>Quests</a></li>
<li><a href='/commands'>Commands</a></li> <li><a href='/commands'>Commands</a></li>
<li><a href='/sessions'>Sessions</a></li> <li><a href='/sessions'>Sessions</a></li>
</ul> </ul>
@@ -0,0 +1,20 @@
<h1>Quests</h1>
<div>
<table>
<tr>
<th>Id</th>
<th>Started</th>
<th>Completed</th>
<th>Failed</th>
<th>Completed Rate</th>
</tr>
{{#each quests}}
<tr>
<td>{{_id}}</td>
<td>{{started}}</td>
<td>{{completed}}</td>
<td>{{failed}}</td>
<td>{{completedRate}} %</td>
</tr>
{{/each}}
</table>
@@ -36,20 +36,20 @@
<table> <table>
<tr> <tr>
<th>Command</th> <th>Stage</th>
<th>PWD</th> <th>PWD</th>
<th>Input</th>
<th>Output</th> <th>Output</th>
<th>Error</th> <th>Error</th>
<th>Stage</th> <th>Input Time</th>
<th>Created At</th>
</tr> </tr>
{{#each commands}} {{#each commands}}
<tr> <tr>
<td>{{command}}</td> <td>{{stage}}</td>
<td>{{pwd}}</td> <td>{{pwd}}</td>
<td>{{command}}</td>
<td>{{output}}</td> <td>{{output}}</td>
<td>{{error}}</td> <td>{{error}}</td>
<td>{{stage}}</td>
<td>{{createdAt}}</td> <td>{{createdAt}}</td>
</tr> </tr>
{{/each}} {{/each}}
+9 -2
View File
@@ -1,27 +1,34 @@
<h1>Sessions</h1> <h1>Sessions</h1>
<div class='pagination'>
<a href='/sessions?order=asc'>Ascending</a>
<a href='/sessions?order=desc'>Descending</a>
</div>
<div> <div>
<table> <table>
<tr> <tr>
<th>User</th> <th>User</th>
<th>Quest</th> <th>Quest</th>
<th></th>
<th>Status</th> <th>Status</th>
<th>Created At</th> <th>Created At</th>
<th>Finished At</th> <th>Finished At</th>
<th>Used Time</th> <th>Used Time</th>
<th>Command Count</th> <th>Command Count</th>
<th>Session</th>
</tr> </tr>
{{#each sessions}} {{#each sessions}}
<tr> <tr>
<td>{{user}}</td> <td>{{user}}</td>
<td>{{quest}}</td> <td>{{quest}}</td>
<td><a href='/sessions/{{_id}}'>Detail</a></td>
<td>{{status}}</td> <td>{{status}}</td>
<td>{{createdAt}}</td> <td>{{createdAt}}</td>
<td>{{finishedAt}}</td> <td>{{finishedAt}}</td>
<td>{{usedTime}}</td> <td>{{usedTime}}</td>
<td>{{commandCount}}</td> <td>{{commandCount}}</td>
<td><a href='/sessions/{{_id}}'>Detail</a></td>
</tr> </tr>
{{/each}} {{/each}}
</table> </table>
</div>
<div class='pagination'>
<a href='/sessions?nextKey={{nextKey}}&order={{order}}'>Next</a>
</div> </div>
+16 -9
View File
@@ -1,23 +1,30 @@
<h1>Users List</h1> <h1>Users List</h1>
<div class='pagination'>
<a href='/users?order=asc'>Ascending</a>
<a href='/users?order=desc'>Descending</a>
</div>
<div> <div>
<table> <table>
<tr> <tr>
<th>Username</th> <th>Username</th>
<th>Email</th>
<th>Registration Method</th>
<th>Registration Time</th>
<th>Sessions Count</th>
<th>Last Activity</th> <th>Last Activity</th>
<th>Sessions Count</th>
<th>Email</th>
<th>Registration Time</th>
<th>Registration Method</th>
</tr> </tr>
{{#each users}} {{#each users}}
<tr> <tr>
<td>{{username}}</td> <td href='/users/{{_id}}'>{{username}}</td>
<td>{{email}}</td>
<td>{{method}}</td>
<td>{{createdAt}}</td>
<td>{{sessionCount}}</td>
<td>{{lastActivityAt}}</td> <td>{{lastActivityAt}}</td>
<td>{{sessionCount}}</td>
<td>{{email}}</td>
<td>{{createdAt}}</td>
<td>{{method}}</td>
</tr> </tr>
{{/each}} {{/each}}
</table> </table>
</div>
<div class='pagination'>
<a href='/users?nextKey={{nextKey}}&order={{order}}'>Next</a>
</div> </div>
+1 -1
View File
@@ -6,6 +6,6 @@
"license": "GPL", "license": "GPL",
"exports": "./index.js", "exports": "./index.js",
"dependencies": { "dependencies": {
"mongoose": "^7.6.0" "mongoose": "^8.0.3"
} }
} }
+1 -1
View File
@@ -26,7 +26,7 @@
"express-validator": "^7.0.1", "express-validator": "^7.0.1",
"jsonwebtoken": "^9.0.1", "jsonwebtoken": "^9.0.1",
"minimist": "^1.2.8", "minimist": "^1.2.8",
"mongoose": "^7.4.0", "mongoose": "^8.0.3",
"node-schedule": "^2.1.1", "node-schedule": "^2.1.1",
"passport": "^0.6.0", "passport": "^0.6.0",
"passport-github2": "^0.1.12", "passport-github2": "^0.1.12",
+16 -72
View File
@@ -1756,15 +1756,10 @@ bser@2.1.1:
dependencies: dependencies:
node-int64 "^0.4.0" node-int64 "^0.4.0"
bson@^5.4.0: bson@^6.2.0:
version "5.4.0" version "6.2.0"
resolved "https://registry.yarnpkg.com/bson/-/bson-5.4.0.tgz#0eea77276d490953ad8616b483298dbff07384c6" resolved "https://registry.yarnpkg.com/bson/-/bson-6.2.0.tgz#4b6acafc266ba18eeee111373c2699304a9ba0a3"
integrity sha512-WRZ5SQI5GfUuKnPTNmAYPiKIof3ORXAF4IRU5UcgmivNIon01rWQlw5RUH954dpu8yGL8T59YShVddIPaU/gFA== integrity sha512-ID1cI+7bazPDyL9wYy9GaQ8gEEohWvcUl/Yf0dIdutJxnmInEEyCsb4awy/OiBfall7zBA179Pahi3vCdFze3Q==
bson@^5.5.0:
version "5.5.0"
resolved "https://registry.yarnpkg.com/bson/-/bson-5.5.0.tgz#a419cc69f368d2def3b8b22ea03ed1c9be40e53f"
integrity sha512-B+QB4YmDx9RStKv8LLSl/aVIEV3nYJc3cJNNTK2Cd1TL+7P+cNpw9mAPeCgc5K+j01Dv6sxUzcITXDx7ZU3F0w==
buffer-crc32@~0.2.3: buffer-crc32@~0.2.3:
version "0.2.13" version "0.2.13"
@@ -3668,11 +3663,6 @@ internal-slot@^1.0.5:
has "^1.0.3" has "^1.0.3"
side-channel "^1.0.4" side-channel "^1.0.4"
ip@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/ip/-/ip-2.0.0.tgz#4cf4ab182fee2314c75ede1276f8c80b479936da"
integrity sha512-WKa+XuLG1A1R0UWhl2+1XQSi+fZWMsYKffMZTTYsiZaUD8k2yDAj5atimTUD2TZkyCkNEeYE5NhFZmupOGtjYQ==
ipaddr.js@1.9.1: ipaddr.js@1.9.1:
version "1.9.1" version "1.9.1"
resolved "https://registry.yarnpkg.com/ipaddr.js/-/ipaddr.js-1.9.1.tgz#bff38543eeb8984825079ff3a2a8e6cbd46781b3" resolved "https://registry.yarnpkg.com/ipaddr.js/-/ipaddr.js-1.9.1.tgz#bff38543eeb8984825079ff3a2a8e6cbd46781b3"
@@ -4833,49 +4823,23 @@ mongodb-connection-string-url@^2.6.0:
"@types/whatwg-url" "^8.2.1" "@types/whatwg-url" "^8.2.1"
whatwg-url "^11.0.0" whatwg-url "^11.0.0"
mongodb@5.7.0: mongodb@6.2.0:
version "5.7.0" version "6.2.0"
resolved "https://registry.yarnpkg.com/mongodb/-/mongodb-5.7.0.tgz#e16d2fcdfd9f8503ec2d88288392dc3235bb3ecc" resolved "https://registry.yarnpkg.com/mongodb/-/mongodb-6.2.0.tgz#2c9dcb3eeaf528ed850e94b3df392de6c6b0d7ab"
integrity sha512-zm82Bq33QbqtxDf58fLWBwTjARK3NSvKYjyz997KSy6hpat0prjeX/kxjbPVyZY60XYPDNETaHkHJI2UCzSLuw== integrity sha512-d7OSuGjGWDZ5usZPqfvb36laQ9CPhnWkAGHT61x5P95p/8nMVeH8asloMwW6GcYFeB0Vj4CB/1wOTDG2RA9BFA==
dependencies: dependencies:
bson "^5.4.0"
mongodb-connection-string-url "^2.6.0"
socks "^2.7.1"
optionalDependencies:
saslprep "^1.0.3"
mongodb@5.9.0:
version "5.9.0"
resolved "https://registry.yarnpkg.com/mongodb/-/mongodb-5.9.0.tgz#5a22065fa8cfaf1d58bf2e3c451cd2c4bfa983a2"
integrity sha512-g+GCMHN1CoRUA+wb1Agv0TI4YTSiWr42B5ulkiAfLLHitGK1R+PkSAf3Lr5rPZwi/3F04LiaZEW0Kxro9Fi2TA==
dependencies:
bson "^5.5.0"
mongodb-connection-string-url "^2.6.0"
socks "^2.7.1"
optionalDependencies:
"@mongodb-js/saslprep" "^1.1.0" "@mongodb-js/saslprep" "^1.1.0"
bson "^6.2.0"
mongodb-connection-string-url "^2.6.0"
mongoose@^7.4.0: mongoose@^8.0.3:
version "7.4.0" version "8.0.3"
resolved "https://registry.yarnpkg.com/mongoose/-/mongoose-7.4.0.tgz#3726a8dea0c9e2d54651845cc84c813a6507906a" resolved "https://registry.yarnpkg.com/mongoose/-/mongoose-8.0.3.tgz#f3708ba0f40b6dec8f5f1a5d40abd69a80207114"
integrity sha512-oHE1eqodfKzugXRlQxpo+msIea7jPcRoayDuEMr50+bYwM/juA5f+1stjkWlXcg6vo1PdJFVA6DGaKOPLuG5mA== integrity sha512-LJRT0yP4TW14HT4r2RkxqyvoTylMSzWpl5QOeVHTnRggCLQSpkoBdgbUtORFq/mSL2o9cLCPJz+6uzFj25qbHw==
dependencies: dependencies:
bson "^5.4.0" bson "^6.2.0"
kareem "2.5.1" kareem "2.5.1"
mongodb "5.7.0" mongodb "6.2.0"
mpath "0.9.0"
mquery "5.0.0"
ms "2.1.3"
sift "16.0.1"
mongoose@^7.6.0:
version "7.6.0"
resolved "https://registry.yarnpkg.com/mongoose/-/mongoose-7.6.0.tgz#3037b754ccdf168385e52d4bcabeb7f5c3b17a5f"
integrity sha512-ztQ12rm0BQN5i7LB6xhWX4l9a9w2aa3jEwa/mM2vAutYJRyAwOzcusvKJBULMzFHyUDBOVW15grisexypgMIWA==
dependencies:
bson "^5.4.0"
kareem "2.5.1"
mongodb "5.9.0"
mpath "0.9.0" mpath "0.9.0"
mquery "5.0.0" mquery "5.0.0"
ms "2.1.3" ms "2.1.3"
@@ -5758,13 +5722,6 @@ safe-stable-stringify@^2.3.1:
resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a" resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a"
integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg== integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==
saslprep@^1.0.3:
version "1.0.3"
resolved "https://registry.yarnpkg.com/saslprep/-/saslprep-1.0.3.tgz#4c02f946b56cf54297e347ba1093e7acac4cf226"
integrity sha512-/MY/PEMbk2SuY5sScONwhUDsV2p77Znkb/q3nSVstq/yQzYJOH/Azh29p9oJLsl3LnQwSvZDKagDGBsBwSooag==
dependencies:
sparse-bitfield "^3.0.3"
semver@^6.0.0, semver@^6.3.0, semver@^6.3.1: semver@^6.0.0, semver@^6.3.0, semver@^6.3.1:
version "6.3.1" version "6.3.1"
resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.1.tgz#556d2ef8689146e46dcea4bfdd095f3434dffcb4" resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.1.tgz#556d2ef8689146e46dcea4bfdd095f3434dffcb4"
@@ -5913,11 +5870,6 @@ slice-ansi@^4.0.0:
astral-regex "^2.0.0" astral-regex "^2.0.0"
is-fullwidth-code-point "^3.0.0" is-fullwidth-code-point "^3.0.0"
smart-buffer@^4.2.0:
version "4.2.0"
resolved "https://registry.yarnpkg.com/smart-buffer/-/smart-buffer-4.2.0.tgz#6e1d71fa4f18c05f7d0ff216dd16a481d0e8d9ae"
integrity sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg==
socket.io-adapter@~2.5.2: socket.io-adapter@~2.5.2:
version "2.5.2" version "2.5.2"
resolved "https://registry.yarnpkg.com/socket.io-adapter/-/socket.io-adapter-2.5.2.tgz#5de9477c9182fdc171cd8c8364b9a8894ec75d12" resolved "https://registry.yarnpkg.com/socket.io-adapter/-/socket.io-adapter-2.5.2.tgz#5de9477c9182fdc171cd8c8364b9a8894ec75d12"
@@ -5956,14 +5908,6 @@ socket.io@^4.7.1:
socket.io-adapter "~2.5.2" socket.io-adapter "~2.5.2"
socket.io-parser "~4.2.4" socket.io-parser "~4.2.4"
socks@^2.7.1:
version "2.7.1"
resolved "https://registry.yarnpkg.com/socks/-/socks-2.7.1.tgz#d8e651247178fde79c0663043e07240196857d55"
integrity sha512-7maUZy1N7uo6+WVEX6psASxtNlKaNVMlGQKkG/63nEDdLOWNbiUMoLK7X4uYoLhQstau72mLgfEWcXcwsaHbYQ==
dependencies:
ip "^2.0.0"
smart-buffer "^4.2.0"
sorted-array-functions@^1.3.0: sorted-array-functions@^1.3.0:
version "1.3.0" version "1.3.0"
resolved "https://registry.yarnpkg.com/sorted-array-functions/-/sorted-array-functions-1.3.0.tgz#8605695563294dffb2c9796d602bd8459f7a0dd5" resolved "https://registry.yarnpkg.com/sorted-array-functions/-/sorted-array-functions-1.3.0.tgz#8605695563294dffb2c9796d602bd8459f7a0dd5"