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" /> --> -

+ Linux Odyssey + +

{{ title }}

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 @@ + + + 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') })