From d3fb6cb7f5fe4d5aabbcc8592411d1fd8946d7bc Mon Sep 17 00:00:00 2001 From: Alex-GF Date: Fri, 27 Mar 2026 18:38:32 +0100 Subject: [PATCH 1/3] refactor: removed unnecesary comment in frontend Dockerfile --- frontend/docker/Dockerfile | 15 --------------- 1 file changed, 15 deletions(-) diff --git a/frontend/docker/Dockerfile b/frontend/docker/Dockerfile index 12a1915..b5de0b1 100644 --- a/frontend/docker/Dockerfile +++ b/frontend/docker/Dockerfile @@ -36,20 +36,5 @@ ENV BASE_PATH=${VITE_BASE_PATH} RUN pnpm run build RUN pnpm add -D serve -# Conditional Reorganization Block: -# This only runs if VITE_FRONTEND_BASE_PATH is defined and is NOT equal to "/" -# RUN if [ -n "$VITE_FRONTEND_BASE_PATH" ] && [ "$VITE_FRONTEND_BASE_PATH" != "/" ]; then \ -# # Extract the directory name (e.g., from "/space/" to "space") to exclude it from the find command -# FOLDER_NAME=$(echo "$VITE_FRONTEND_BASE_PATH" | sed 's/\///g'); \ -# echo "Reorganizing dist for subpath: $VITE_FRONTEND_BASE_PATH"; \ -# # Create the nested directory structure -# mkdir -p ./dist${VITE_FRONTEND_BASE_PATH} && \ -# # Move everything EXCEPT index.html and the newly created subfolder into the subfolder -# find ./dist -mindepth 1 -maxdepth 1 ! -name "index.html" ! -name "$FOLDER_NAME" \ -# -exec mv -t ./dist${VITE_FRONTEND_BASE_PATH}/ {} +; \ -# else \ -# echo "Skipping reorganization: App will be served from root (/)"; \ -# fi - # Specify the command to start the server CMD ["pnpm", "exec", "serve", "-s", "./dist", "-l", "5050"] \ No newline at end of file From e98d6932363cbeaa5ea244feadea5ce8b7138d09 Mon Sep 17 00:00:00 2001 From: Alex-GF Date: Mon, 30 Mar 2026 18:09:09 +0200 Subject: [PATCH 2/3] fix: delete of last admin error --- api/src/main/repositories/mongoose/UserRepository.ts | 9 +++++++++ api/src/main/services/UserService.ts | 5 ++--- 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/api/src/main/repositories/mongoose/UserRepository.ts b/api/src/main/repositories/mongoose/UserRepository.ts index cc3522b..b78c883 100644 --- a/api/src/main/repositories/mongoose/UserRepository.ts +++ b/api/src/main/repositories/mongoose/UserRepository.ts @@ -7,6 +7,15 @@ import { generateUserApiKey } from '../../utils/users/helpers'; class UserRepository extends RepositoryBase { + async findAll(query?: any): Promise { + try { + const users = await UserMongoose.find(query || {}, { password: 0 }).exec(); + return users.map(user => user.toObject({ getters: true, virtuals: true, versionKey: false }) as unknown as LeanUser); + } catch (err) { + return []; + } + } + async find(username: string, limit: number = 10, offset: number = 0): Promise { try { const users = await UserMongoose.find({ username: { $regex: username, $options: 'i' } }, { password: 0 }).skip(offset).limit(limit).exec(); diff --git a/api/src/main/services/UserService.ts b/api/src/main/services/UserService.ts index 0c641e5..56dd11f 100644 --- a/api/src/main/services/UserService.ts +++ b/api/src/main/services/UserService.ts @@ -176,9 +176,8 @@ class UserService { if (user.role === 'ADMIN') { // Contar admins restantes - const allUsers = await this.userRepository.findAll(); - const adminCount = allUsers.filter((u: LeanUser) => u.role === 'ADMIN' && u.username !== username).length; - if (adminCount < 1) { + const admins = await this.userRepository.findAll({role: 'ADMIN'}); + if (admins.length < 2) { throw new Error('PERMISSION ERROR: There must always be at least one ADMIN user in the system.'); } } From 0c561c2daf032b88ef8ad316e48511955c4dbe9d Mon Sep 17 00:00:00 2001 From: Alex-GF Date: Mon, 30 Mar 2026 18:13:23 +0200 Subject: [PATCH 3/3] test: delete last admin test --- api/src/test/user.test.ts | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/api/src/test/user.test.ts b/api/src/test/user.test.ts index 35d62f9..8bc794e 100644 --- a/api/src/test/user.test.ts +++ b/api/src/test/user.test.ts @@ -939,6 +939,36 @@ describe('User API routes', function () { await deleteTestUser(targetAdmin.username); }); + it('returns 403 when trying to delete the last admin', async function () { + // Get all admins currently in the system + const allUsersResponse = await request(app) + .get(`${baseUrl}/users?limit=50`) + .set('x-api-key', adminApiKey); + + const allAdmins = allUsersResponse.body.data.filter((u: any) => u.role === 'ADMIN'); + + // Delete all admins except the last one + for (let i = 0; i < allAdmins.length - 1; i++) { + await deleteTestUser(allAdmins[i].username); + } + + const lastAdminUsername = allAdmins[allAdmins.length - 1].username; + + // Try to delete the last admin - should fail + const response = await request(app) + .delete(`${baseUrl}/users/${lastAdminUsername}`) + .set('x-api-key', adminApiKey); + + expect(response.status).toBe(403); + expect(response.body.error).toBeDefined(); + + // Verify the last admin still exists + const getResponse = await request(app) + .get(`${baseUrl}/users/${lastAdminUsername}`) + .set('x-api-key', adminApiKey); + expect(getResponse.status).toBe(200); + }); + it('returns 401 when api key is missing', async function () { const testUser = await createTestUser('USER'); trackUserForCleanup(testUser);