-
-
+
+ Quest Map
Unix編年史
-
+ -->
diff --git a/server/src/api/controllers/leaderboardController.js b/server/src/api/controllers/leaderboardController.js
new file mode 100644
index 0000000..4f497a1
--- /dev/null
+++ b/server/src/api/controllers/leaderboardController.js
@@ -0,0 +1,12 @@
+import { asyncHandler } from '../../middleware/error.js'
+import { leaderboard } from '../../models/userManager.js'
+
+export default asyncHandler(async (req, res) => {
+ const users = await leaderboard()
+ const response = users.map((user) => ({
+ username: user.user.username,
+ completedQuests: user.completedQuests,
+ score: user.score * 1000,
+ }))
+ res.json(response)
+})
diff --git a/server/src/api/routes/index.js b/server/src/api/routes/index.js
index 427cf91..2cfd5ec 100644
--- a/server/src/api/routes/index.js
+++ b/server/src/api/routes/index.js
@@ -7,6 +7,7 @@ import authRouter from './authRouter.js'
import authRequired from '../../middleware/authRequired.js'
import userRouter from './userRouter.js'
import config from '../../config.js'
+import leaderboardController from '../controllers/leaderboardController.js'
const router = Router()
@@ -48,4 +49,6 @@ router.get('/bug-report', (req, res) => {
res.redirect(config.bugReportUrl)
})
+router.get('/leaderboard', leaderboardController)
+
export default router
diff --git a/server/src/models/userManager.js b/server/src/models/userManager.js
index 46943dd..d2f5faf 100644
--- a/server/src/models/userManager.js
+++ b/server/src/models/userManager.js
@@ -22,3 +22,46 @@ export async function createUser(username, email, { password, socialLogin }) {
return user
}
+
+export function leaderboard() {
+ return UserProfile.aggregate([
+ {
+ $project: {
+ user: true, // Include the user field in the projection
+ completedQuests: {
+ $map: {
+ input: {
+ $filter: {
+ input: { $objectToArray: '$progress' }, // Convert the progress map to an array
+ as: 'prog',
+ cond: { $eq: ['$$prog.v.completed', true] }, // Filter for completed quests
+ },
+ },
+ as: 'completedProg',
+ in: '$$completedProg.k', // Extract the quest IDs
+ },
+ },
+ },
+ },
+ {
+ $addFields: {
+ score: { $size: '$completedQuests' }, // Calculate the size of the completedQuests array
+ },
+ },
+ {
+ $match: {
+ score: { $gt: 0 }, // Filter out users who haven't completed any quests
+ },
+ },
+ { $sort: { score: -1 } },
+ {
+ $lookup: {
+ from: 'users',
+ localField: 'user',
+ foreignField: '_id',
+ as: 'user',
+ },
+ },
+ { $unwind: '$user' },
+ ])
+}
diff --git a/tests/cypress/e2e/helloworld.cy.js b/tests/cypress/e2e/helloworld.cy.js
index e6e11f2..40d104e 100644
--- a/tests/cypress/e2e/helloworld.cy.js
+++ b/tests/cypress/e2e/helloworld.cy.js
@@ -79,7 +79,7 @@ describe('example helloworld app', () => {
cy.PrepareForGame()
})
it('Check Header', () => {
- cy.get('#HeaderText').should('contain', title).and('be.visible')
+ cy.get('#HeaderText').should('contain', 'Linux Odyssey').and('be.visible')
cy.findByRole('link', { name: 'Bug Report' }).should('be.visible')
cy.findByRole('button', { name: 'Sign Out' }).should('be.visible')
})
@@ -123,8 +123,8 @@ describe('example helloworld app', () => {
it('Typing in Terminal', () => {
cy.typeInCommand('12345{enter}')
cy.get('@Terminaltextbox')
- .should('contain', 'zsh: command not found: 12345')
- .and('contain', '12345')
+ .should('contain', '12345')
+ .and('contain', 'zsh: command not found: 12345', { timeout: 50000 })
})
it('Complete the Game(relating UI)', () => {
// Stage1
diff --git a/tests/cypress/support/commands.js b/tests/cypress/support/commands.js
index a084dd9..6c28baa 100644
--- a/tests/cypress/support/commands.js
+++ b/tests/cypress/support/commands.js
@@ -9,12 +9,19 @@ Cypress.Commands.add('LoginWithPassword', (username, password) => {
})
Cypress.Commands.add('PrepareForGame', () => {
cy.visit('/')
+ // make sure the login page is loaded
+ cy.url().should('satisfy', (elements) => {
+ const text = elements
+ return text.includes('login') || text.includes('register')
+ })
cy.LoginWithPassword(
Cypress.env('defaultAccount'),
Cypress.env('defaultPassword')
)
+ // make sure the map page is loaded
cy.url().should('include', '/map')
cy.visit('/game/get-started')
+ cy.url().should('include', '/game/get-started')
cy.get('.xterm-screen', { timeout: 10000 })
.as('Terminaltextbox')
.should('be.visible')
@@ -24,10 +31,6 @@ Cypress.Commands.add('PrepareForGame', () => {
'contain',
'commander:~ $'
)
- cy.get('@Terminaltextbox', { timeout: 150000 }).should(
- 'contain',
- 'commander:~ $'
- )
})
Cypress.Commands.add('typeInCommand', (command) => {
cy.get('.xterm-screen', { timeout: 150000 }).type(command)
diff --git a/yarn.lock b/yarn.lock
index 98b13b0..e5c7d07 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -1500,12 +1500,7 @@ astral-regex@^2.0.0:
resolved "https://registry.yarnpkg.com/astral-regex/-/astral-regex-2.0.0.tgz#483143c567aeed4785759c0865786dc77d7d2e31"
integrity sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ==
-async@^3.2.0:
- version "3.2.5"
- resolved "https://registry.yarnpkg.com/async/-/async-3.2.5.tgz#ebd52a8fdaf7a2289a24df399f8d8485c8a46b66"
- integrity sha512-baNZyqaaLhyLVKm/DlvdW051MSgO6b8eVfIezl9E5PqWxFgzLm/wQntEW4zOytVburDEr0JlALEpdOFwvErLsg==
-
-async@^3.2.3:
+async@^3.2.0, async@^3.2.3:
version "3.2.5"
resolved "https://registry.yarnpkg.com/async/-/async-3.2.5.tgz#ebd52a8fdaf7a2289a24df399f8d8485c8a46b66"
integrity sha512-baNZyqaaLhyLVKm/DlvdW051MSgO6b8eVfIezl9E5PqWxFgzLm/wQntEW4zOytVburDEr0JlALEpdOFwvErLsg==
@@ -4556,6 +4551,11 @@ locate-path@^6.0.0:
dependencies:
p-locate "^5.0.0"
+lodash-es@^4.17.21:
+ version "4.17.21"
+ resolved "https://registry.yarnpkg.com/lodash-es/-/lodash-es-4.17.21.tgz#43e626c46e6591b7750beb2b50117390c609e3ee"
+ integrity sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw==
+
lodash.includes@^4.3.0:
version "4.3.0"
resolved "https://registry.yarnpkg.com/lodash.includes/-/lodash.includes-4.3.0.tgz#60bb98a87cb923c68ca1e51325483314849f553f"
@@ -4636,6 +4636,11 @@ long-timeout@0.1.1:
resolved "https://registry.yarnpkg.com/long-timeout/-/long-timeout-0.1.1.tgz#9721d788b47e0bcb5a24c2e2bee1a0da55dab514"
integrity sha512-BFRuQUqc7x2NWxfJBCyUrN8iYUYznzL9JROmRz1gZ6KlOIgmoD+njPVbb+VNn2nGMKggMsK79iUNErillsrx7w==
+lottie-web@5.12.2:
+ version "5.12.2"
+ resolved "https://registry.yarnpkg.com/lottie-web/-/lottie-web-5.12.2.tgz#579ca9fe6d3fd9e352571edd3c0be162492f68e5"
+ integrity sha512-uvhvYPC8kGPjXT3MyKMrL3JitEAmDMp30lVkuq/590Mw9ok6pWcFCwXJveo0t5uqYw1UREQHofD+jVpdjBv8wg==
+
lru-cache@^5.1.1:
version "5.1.1"
resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-5.1.1.tgz#1da27e6710271947695daf6848e847f01d84b920"
@@ -6620,6 +6625,14 @@ vue-toastification@^2.0.0-rc.5:
resolved "https://registry.yarnpkg.com/vue-toastification/-/vue-toastification-2.0.0-rc.5.tgz#92798604d806ae473cfb76ed776fae294280f8f8"
integrity sha512-q73e5jy6gucEO/U+P48hqX+/qyXDozAGmaGgLFm5tXX4wJBcVsnGp4e/iJqlm9xzHETYOilUuwOUje2Qg1JdwA==
+vue3-lottie@^3.2.0:
+ version "3.2.0"
+ resolved "https://registry.yarnpkg.com/vue3-lottie/-/vue3-lottie-3.2.0.tgz#7f95831d3483dc304c680a9b2cac6ed098070a2a"
+ integrity sha512-PtNXbv7iD4XGmK8CXd71aLWmILIR6P6VzLMDw6ZzmDAeuJZuJ0MdOVnn50LOARgq+cOFJsn/2z+7tZh0F9T9qw==
+ dependencies:
+ lodash-es "^4.17.21"
+ lottie-web "5.12.2"
+
vue@^3.3.4:
version "3.3.4"
resolved "https://registry.yarnpkg.com/vue/-/vue-3.3.4.tgz#8ed945d3873667df1d0fcf3b2463ada028f88bd6"