Files
Gov_Travel_App/utils/validation.js
mblanke 15094ac94b Add Python web scraper for NJC travel rates with currency extraction
- Implemented Python scraper using BeautifulSoup and pandas to automatically collect travel rates from official NJC website
- Added currency extraction from table titles (supports EUR, USD, AUD, CAD, ARS, etc.)
- Added country extraction from table titles for international rates
- Flatten pandas MultiIndex columns for cleaner data structure
- Default to CAD for domestic Canadian sources (accommodations and domestic tables)
- Created SQLite database schema (raw_tables, rate_entries, exchange_rates, accommodations)
- Successfully scraped 92 tables with 17,205 rate entries covering 25 international cities
- Added migration script to convert scraped data to Node.js database format
- Updated .gitignore for Python files (.venv/, __pycache__, *.pyc, *.sqlite3)
- Fixed city validation and currency conversion in main app
- Added comprehensive debug and verification scripts

This replaces manual JSON maintenance with automated data collection from official government source.
2026-01-13 09:21:43 -05:00

130 lines
3.4 KiB
JavaScript

const Joi = require('joi');
// Flight search validation
const flightSearchSchema = Joi.object({
origin: Joi.string()
.min(2)
.max(100)
.required()
.trim()
.messages({
'string.empty': 'Origin city is required',
'string.min': 'Origin city must be at least 2 characters',
'string.max': 'Origin city cannot exceed 100 characters'
}),
destination: Joi.string()
.min(2)
.max(100)
.required()
.trim()
.messages({
'string.empty': 'Destination city is required',
'string.min': 'Destination city must be at least 2 characters',
'string.max': 'Destination city cannot exceed 100 characters'
}),
departureDate: Joi.date()
.iso()
.min('now')
.required()
.messages({
'date.base': 'Departure date must be a valid date',
'date.min': 'Departure date cannot be in the past',
'any.required': 'Departure date is required'
}),
returnDate: Joi.date()
.iso()
.min(Joi.ref('departureDate'))
.optional()
.allow(null, '')
.messages({
'date.base': 'Return date must be a valid date',
'date.min': 'Return date must be after departure date'
}),
adults: Joi.number()
.integer()
.min(1)
.max(9)
.default(1)
.messages({
'number.base': 'Number of adults must be a number',
'number.min': 'At least 1 adult is required',
'number.max': 'Maximum 9 adults allowed'
})
});
// Accommodation search validation
const accommodationSearchSchema = Joi.object({
city: Joi.string()
.min(2)
.max(100)
.required()
.trim()
.messages({
'string.empty': 'City name is required',
'string.min': 'City name must be at least 2 characters',
'string.max': 'City name cannot exceed 100 characters'
})
});
// City key validation
const cityKeySchema = Joi.object({
cityKey: Joi.string()
.min(2)
.max(100)
.required()
.trim()
.messages({
'string.empty': 'City key is required'
})
});
// Month validation
const monthSchema = Joi.object({
cityKey: Joi.string().required(),
month: Joi.number()
.integer()
.min(1)
.max(12)
.required()
.messages({
'number.min': 'Month must be between 1 and 12',
'number.max': 'Month must be between 1 and 12',
'any.required': 'Month is required'
})
});
// Validation middleware factory
const validate = (schema) => {
return (req, res, next) => {
const { error, value } = schema.validate(req.query, {
abortEarly: false,
stripUnknown: true
});
if (error) {
const errors = error.details.map(detail => ({
field: detail.path.join('.'),
message: detail.message
}));
return res.status(400).json({
success: false,
message: 'Validation failed',
errors
});
}
// Replace req.query with validated and sanitized values
req.query = value;
next();
};
};
module.exports = {
validate,
flightSearchSchema,
accommodationSearchSchema,
cityKeySchema,
monthSchema
};