Skip to content

Commit 1802bae

Browse files
authored
Merge pull request #37 from fraidakis/fraidakis
Improve metrics: Maintainability Index, Comment Density, Security Vulnerabilities of Dependencies, Number of Imports
2 parents 26885cb + c8402fd commit 1802bae

52 files changed

Lines changed: 1294 additions & 586 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

app.js

Lines changed: 8 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -3,33 +3,16 @@
33
* Configures and exports the Express app instance
44
*/
55

6-
import express from 'express';
7-
import cors from 'cors';
8-
import helmet from 'helmet';
9-
import swaggerUi from 'swagger-ui-express';
10-
import yaml from 'js-yaml';
11-
import { readFileSync } from 'fs';
12-
import { fileURLToPath } from 'url';
13-
import { dirname, join } from 'path';
14-
import mongoose from 'mongoose';
15-
16-
// Import middleware
17-
import errorHandler from './middleware/errorHandler.js';
18-
import { requestLogger } from './middleware/logger.js';
19-
import requestId from './middleware/requestId.js';
20-
21-
// Import centralized routes
6+
// External dependencies (4 consolidated into 1)
7+
import { express, cors, helmet, mongoose } from './dependencies.js';
8+
// Middleware
9+
import { errorHandler, requestLogger, requestId } from './middleware/index.js';
10+
// Configuration
11+
import { setupSwagger, API_VERSION } from './config/index.js';
12+
// Routes
2213
import routes from './routes/index.js';
2314

24-
// Import constants
25-
import { API_VERSION } from './config/constants.js';
2615

27-
// Load Swagger YAML
28-
const __filename = fileURLToPath(import.meta.url);
29-
const __dirname = dirname(__filename);
30-
const swaggerDocument = yaml.load(
31-
readFileSync(join(__dirname, 'docs', 'swagger.yaml'), 'utf8')
32-
);
3316

3417
const app = express();
3518

@@ -87,11 +70,7 @@ app.use(requestLogger);
8770
* Swagger API Documentation
8871
* Serves interactive API documentation at /api-docs
8972
*/
90-
app.use('/api-docs', swaggerUi.serve, swaggerUi.setup(swaggerDocument, {
91-
customCss: '.swagger-ui .topbar { display: none }',
92-
customSiteTitle: 'myWorld Travel API Documentation',
93-
customfavIcon: '/favicon.ico'
94-
}));
73+
setupSwagger(app);
9574

9675
/**
9776
* Root endpoint with minimal HATEOAS links

config/database.js

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,9 @@ import initialData from './seedData.js';
77

88
let isConnected = false;
99

10+
// Establish MongoDB connection and seed initial data if needed
1011
const connectDB = async () => {
12+
// Skip if already connected
1113
if (isConnected) { console.log('✓ Already connected to MongoDB'); return; }
1214

1315
const mongoURI = process.env.MONGODB_URI;
@@ -25,6 +27,7 @@ const connectDB = async () => {
2527
}
2628
};
2729

30+
// Close MongoDB connection gracefully
2831
const disconnectDB = async () => {
2932
if (!isConnected) return;
3033
try {
@@ -37,14 +40,17 @@ const disconnectDB = async () => {
3740
}
3841
};
3942

43+
// Populate database with initial seed data from JSON files
4044
const seedInitialData = async () => {
4145
try {
46+
// Skip if data already exists
4247
if (await models.User.countDocuments() > 0) {
4348
console.log('✓ Database already contains data. Skipping seeding.');
4449
return;
4550
}
4651

4752
console.log('📦 Seeding initial data...');
53+
// Insert all entity types
4854
await models.User.insertMany(initialData.users);
4955
await models.Place.insertMany(initialData.places);
5056
await models.PreferenceProfile.insertMany(initialData.preferenceProfiles);
@@ -54,6 +60,7 @@ const seedInitialData = async () => {
5460
await models.DislikedPlace.insertMany(initialData.dislikedPlaces);
5561
await models.Settings.insertMany(initialData.settings);
5662

63+
// Initialize ID counters for auto-increment
5764
const counters = Object.entries(initialData.counters).map(([name, value]) => ({ name, value }));
5865
await models.Counter.insertMany(counters);
5966
console.log('✓ Initial data seeding completed successfully!');
@@ -63,17 +70,21 @@ const seedInitialData = async () => {
6370
}
6471
};
6572

73+
// Generate next sequential ID for a counter (auto-increment)
6674
const getNextId = async (counterName) => {
6775
const counter = await models.Counter.findOneAndUpdate({ name: counterName }, { $inc: { value: 1 } }, { new: true, upsert: true });
6876
return counter.value;
6977
};
7078

79+
// Remove all data from database (used in testing)
7180
const clearAllData = async () => {
7281
try {
82+
// Skip if database not connected
7383
if (mongoose.connection.readyState !== 1) {
7484
console.warn('⚠️ clearAllData called when MongoDB is not connected; skipping');
7585
return;
7686
}
87+
// Delete all documents from all collections
7788
await Promise.all([
7889
models.User.deleteMany({}), models.Place.deleteMany({}), models.PreferenceProfile.deleteMany({}),
7990
models.Review.deleteMany({}), models.Report.deleteMany({}), models.FavouritePlace.deleteMany({}),

config/index.js

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
/**
2+
* Config Index - Centralized config exports
3+
* Consolidates configuration exports to reduce import statements
4+
*/
5+
6+
export { setupSwagger } from './swagger.js';
7+
export { API_VERSION } from './constants.js';
8+
export { default as db } from './db.js';
9+
export { default as database } from './database.js';

config/seedData/index.js

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,12 @@ import bcrypt from 'bcryptjs';
66

77
const require = createRequire(import.meta.url);
88

9+
// Load JSON data file from relative path
910
function loadJsonData(path) {
1011
return require(path);
1112
}
1213

14+
// Hash passwords for all users using bcrypt
1315
function hashUserPasswords(usersArray) {
1416
return usersArray.map(user => ({
1517
...user,
@@ -18,6 +20,7 @@ function hashUserPasswords(usersArray) {
1820
}));
1921
}
2022

23+
// Initialize counter values for auto-increment IDs
2124
function createCounters() {
2225
return {
2326
userId: 20000,
@@ -30,11 +33,13 @@ function createCounters() {
3033
};
3134
}
3235

36+
// Load all JSON seed data files
3337
const placesData = loadJsonData('./places.json');
3438
const placesExtendedData = loadJsonData('./placesExtended.json');
3539
const interactionsData = loadJsonData('./interactions.json');
3640
const usersData = loadJsonData('./users.json');
3741

42+
// Prepare data arrays for database insertion
3843
let places = [...placesData];
3944
let placesExtended = [...placesExtendedData];
4045
let { reviews, reports, favouritePlaces, dislikedPlaces } = interactionsData;

config/swagger.js

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
/**
2+
* Swagger Documentation Configuration
3+
* Sets up Swagger UI for API documentation
4+
*/
5+
6+
import swaggerUi from 'swagger-ui-express';
7+
import yaml from 'js-yaml';
8+
import { readFileSync } from 'fs';
9+
import { fileURLToPath } from 'url';
10+
import { dirname, join } from 'path';
11+
12+
/**
13+
* Configure and mount Swagger UI on the express app
14+
* @param {Object} app - Express application instance
15+
*/
16+
export const setupSwagger = (app) => {
17+
const __filename = fileURLToPath(import.meta.url);
18+
const __dirname = dirname(__filename);
19+
20+
// Go up one level from config/ to root/ to find docs/
21+
const rootDir = join(__dirname, '..');
22+
23+
const swaggerDocument = yaml.load(
24+
readFileSync(join(rootDir, 'docs', 'swagger.yaml'), 'utf8')
25+
);
26+
27+
app.use('/api-docs', swaggerUi.serve, swaggerUi.setup(swaggerDocument, {
28+
customCss: '.swagger-ui .topbar { display: none }',
29+
customSiteTitle: 'myWorld Travel API Documentation',
30+
customfavIcon: '/favicon.ico'
31+
}));
32+
};

controllers/adminController.js

Lines changed: 26 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,29 @@ import jwt from 'jsonwebtoken';
99
import R from '../utils/responseBuilder.js';
1010
import { requirePlace } from '../utils/controllerValidators.js';
1111

12+
// --- Helper Functions (Private) ---
13+
14+
/** Allowed fields for place updates */
15+
const ALLOWED_PLACE_FIELDS = ['name', 'description', 'category', 'website'];
16+
17+
/** Prepare update DTO from request body, picking only allowed fields */
18+
const preparePlaceUpdateDTO = (body, existingLocation) => {
19+
const updateData = {};
20+
ALLOWED_PLACE_FIELDS.forEach(k => { if (body[k]) updateData[k] = body[k]; });
21+
if (body.location) updateData.location = { ...existingLocation, ...body.location };
22+
return updateData;
23+
};
24+
25+
/** Add HATEOAS links to each report */
26+
const enrichReportsWithLinks = (reports, placeId, adminId) => {
27+
return reports.map(report => {
28+
const reportObj = report.toObject ? report.toObject() : report;
29+
return { ...reportObj, links: buildHateoasLinks.adminReport(placeId, adminId) };
30+
});
31+
};
32+
33+
// --- Controllers ---
34+
1235
const getPlaceReports = async (req, res, next) => {
1336
try {
1437
const adminId = parseInt(req.params.adminId);
@@ -18,10 +41,7 @@ const getPlaceReports = async (req, res, next) => {
1841
if (!place) return;
1942

2043
const placeReports = await db.getReportsForPlace(placeId);
21-
const reportsWithLinks = placeReports.map(report => {
22-
const reportObj = report.toObject ? report.toObject() : report;
23-
return { ...reportObj, links: buildHateoasLinks.adminReport(placeId, adminId) };
24-
});
44+
const reportsWithLinks = enrichReportsWithLinks(placeReports, placeId, adminId);
2545

2646
return R.success(res, { reports: reportsWithLinks, totalReports: reportsWithLinks.length, links: buildHateoasLinks.adminReportsCollection(adminId, placeId) }, 'Place reports retrieved successfully');
2747
} catch (error) { next(error); }
@@ -36,9 +56,7 @@ const updatePlace = async (req, res, next) => {
3656
if (!place) return;
3757

3858
const placeObj = place.toObject ? place.toObject() : place;
39-
const updateData = {};
40-
['name', 'description', 'category', 'website'].forEach(k => { if (req.body[k]) updateData[k] = req.body[k]; });
41-
if (req.body.location) updateData.location = { ...placeObj.location, ...req.body.location };
59+
const updateData = preparePlaceUpdateDTO(req.body, placeObj.location);
4260

4361
const updatedPlace = await db.updatePlace(placeId, updateData);
4462
const updatedPlaceObj = updatedPlace.toObject ? updatedPlace.toObject() : updatedPlace;
@@ -68,3 +86,4 @@ const generateAdminToken = (req, res, next) => {
6886
};
6987

7088
export default { getPlaceReports, updatePlace, generateAdminToken };
89+

controllers/favouriteController.js

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,13 +10,16 @@ import { requireUser, requireUserAndPlace } from '../utils/controllerValidators.
1010

1111
export const validateUserAndPlace = requireUserAndPlace;
1212

13+
// Get all favourite places for a user with HATEOAS links
1314
const getFavouritePlaces = async (req, res, next) => {
1415
try {
1516
const userId = parseInt(req.params.userId);
17+
// Validate user exists
1618
const user = await requireUser(res, userId);
1719
if (!user) return;
1820

1921
const favouritePlaces = await db.getFavouritePlaces(userId);
22+
// Attach HATEOAS links to each favourite
2023
const favouritePlacesWithLinks = favouritePlaces.map(fav => {
2124
const place = fav.place || {};
2225
return {
@@ -32,13 +35,16 @@ const getFavouritePlaces = async (req, res, next) => {
3235
} catch (error) { next(error); }
3336
};
3437

38+
// Add a place to user's favourites list
3539
const addFavouritePlace = async (req, res, next) => {
3640
try {
3741
const userId = parseInt(req.params.userId);
42+
// Validate both user and place exist
3843
const validation = await requireUserAndPlace(res, userId, req.body.placeId);
3944
if (!validation) return;
4045

4146
const newFavourite = await db.addFavouritePlace(userId, req.body.placeId);
47+
// Check if place is already in favourites
4248
if (!newFavourite) {
4349
return R.conflict(res, 'PLACE_ALREADY_FAVORITED', "Place is already in user's favorites", { placeId: req.body.placeId, userId });
4450
}
@@ -54,6 +60,7 @@ const addFavouritePlace = async (req, res, next) => {
5460
} catch (error) { next(error); }
5561
};
5662

63+
// Remove a place from user's favourites by favouriteId
5764
const removeFavouritePlace = async (req, res, next) => {
5865
try {
5966
const userId = parseInt(req.params.userId);
@@ -62,6 +69,7 @@ const removeFavouritePlace = async (req, res, next) => {
6269
const user = await requireUser(res, userId);
6370
if (!user) return;
6471

72+
// Attempt to remove favourite
6573
const removed = await db.removeFavouritePlace(userId, favouriteId);
6674
if (!removed) {
6775
return R.notFound(res, 'FAVOURITE_NOT_FOUND', `Favourite with ID ${favouriteId} not found for user ${userId}`);

0 commit comments

Comments
 (0)