diff --git a/app/src/components/GameHeaderPart.vue b/app/src/components/GameHeaderPart.vue
index 3cbf961..3c93ed9 100644
--- a/app/src/components/GameHeaderPart.vue
+++ b/app/src/components/GameHeaderPart.vue
@@ -69,10 +69,17 @@ onMounted(async () => {
class="text-text-tertiary h-full w-full"
/>
-->
-
diff --git a/app/src/router.js b/app/src/router.js
index 76f8280..1d3b420 100644
--- a/app/src/router.js
+++ b/app/src/router.js
@@ -4,6 +4,7 @@ import LoginPage from './views/LoginPage.vue'
import RegisterPage from './views/RegisterPage.vue'
import ChooseUsernamePage from './views/ChooseUsernamePage.vue'
import QuestMapPage from './views/QuestMapPage.vue'
+import LeaderboardPage from './views/LeaderboardPage.vue'
import { isLoggedIn } from './utils/auth'
const routes = [
@@ -45,6 +46,11 @@ const routes = [
component: QuestMapPage,
meta: { requiresAuth: true },
},
+ {
+ path: '/leaderboard',
+ name: 'leaderboard',
+ component: LeaderboardPage,
+ },
]
const router = createRouter({
diff --git a/app/src/views/LeaderboardPage.vue b/app/src/views/LeaderboardPage.vue
new file mode 100644
index 0000000..dcba85e
--- /dev/null
+++ b/app/src/views/LeaderboardPage.vue
@@ -0,0 +1,81 @@
+
+
+
+
+
+
+
+
+
+
+
+
排行榜
+
+
+
+
+ | 使用者名稱 |
+ 分數 |
+ 完成關卡 |
+
+
+
+
+ |
+ {{ user.username }}
+ |
+ {{ user.score }} |
+
+
+ {{ quest }}
+
+ |
+
+
+
+
+
+
+
+
+
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 aaba53f..dd35431 100644
--- a/tests/cypress/e2e/helloworld.cy.js
+++ b/tests/cypress/e2e/helloworld.cy.js
@@ -78,9 +78,7 @@ describe('example helloworld app', () => {
cy.PrepareForGame()
})
it('Check Header', () => {
- cy.get('#HeaderText')
- .should('contain', 'Hello, Linux World!')
- .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')
})