diff --git a/exercisefiles/node/endpoints/calculateMemoryConsumption.js b/exercisefiles/node/endpoints/calculateMemoryConsumption.js new file mode 100644 index 0000000..107e990 --- /dev/null +++ b/exercisefiles/node/endpoints/calculateMemoryConsumption.js @@ -0,0 +1,12 @@ +/** + * GET /CalculateMemoryConsumption + * Reports heap usage in GB + */ +function handleCalculateMemoryConsumption(query, res) { + const memoryUsage = process.memoryUsage(); + const memoryInGB = (memoryUsage.heapUsed / (1024 * 1024 * 1024)).toFixed(2); + res.writeHead(200, { 'Content-Type': 'text/plain' }); + res.end(memoryInGB); +} + +module.exports = handleCalculateMemoryConsumption; diff --git a/exercisefiles/node/endpoints/daysBetweenDates.js b/exercisefiles/node/endpoints/daysBetweenDates.js new file mode 100644 index 0000000..53a5402 --- /dev/null +++ b/exercisefiles/node/endpoints/daysBetweenDates.js @@ -0,0 +1,21 @@ +/** + * GET /DaysBetweenDates?date1=YYYY-MM-DD&date2=YYYY-MM-DD + * Computes absolute difference in days between two dates + */ +function handleDaysBetweenDates(query, res) { + const date1 = query.date1; + const date2 = query.date2; + if (date1 && date2) { + const d1 = new Date(date1); + const d2 = new Date(date2); + const diffTime = Math.abs(d2 - d1); + const diffDays = Math.ceil(diffTime / (1000 * 60 * 60 * 24)); + res.writeHead(200, { 'Content-Type': 'text/plain' }); + res.end(String(diffDays)); + } else { + res.writeHead(400, { 'Content-Type': 'text/plain' }); + res.end('date1 and date2 parameters are required'); + } +} + +module.exports = handleDaysBetweenDates; diff --git a/exercisefiles/node/endpoints/get.js b/exercisefiles/node/endpoints/get.js new file mode 100644 index 0000000..7beca33 --- /dev/null +++ b/exercisefiles/node/endpoints/get.js @@ -0,0 +1,16 @@ +/** + * GET /get?key=VALUE + * Returns 'hello VALUE' if key is provided, otherwise 400 error + */ +function handleGet(query, res) { + const key = query.key; + if (key) { + res.writeHead(200, { 'Content-Type': 'text/plain' }); + res.end('hello ' + key); + } else { + res.writeHead(400, { 'Content-Type': 'text/plain' }); + res.end('key not passed'); + } +} + +module.exports = handleGet; diff --git a/exercisefiles/node/endpoints/getFullTextFile.js b/exercisefiles/node/endpoints/getFullTextFile.js new file mode 100644 index 0000000..05340b5 --- /dev/null +++ b/exercisefiles/node/endpoints/getFullTextFile.js @@ -0,0 +1,20 @@ +const fs = require('fs'); + +/** + * GET /GetFullTextFile + * Reads entire file and filters lines containing 'Fusce' + */ +function handleGetFullTextFile(query, res, dirname) { + fs.readFile(dirname + '/sample.txt', 'utf8', (err, data) => { + if (err) { + res.writeHead(500, { 'Content-Type': 'text/plain' }); + res.end('Error reading file'); + return; + } + const lines = data.split('\n').filter(line => line.includes('Fusce')); + res.writeHead(200, { 'Content-Type': 'text/plain' }); + res.end(lines.join('\n')); + }); +} + +module.exports = handleGetFullTextFile; diff --git a/exercisefiles/node/endpoints/getLineByLineFromTextFile.js b/exercisefiles/node/endpoints/getLineByLineFromTextFile.js new file mode 100644 index 0000000..70634fb --- /dev/null +++ b/exercisefiles/node/endpoints/getLineByLineFromTextFile.js @@ -0,0 +1,42 @@ +const fs = require('fs'); +const readline = require('readline'); + +/** + * GET /GetLineByLinefromtTextFile + * Streams file line-by-line and collects lines containing 'Fusce' + */ +function handleGetLineByLineFromTextFile(query, res, dirname) { + const readFileLineByLine = () => { + return new Promise((resolve, reject) => { + const fileStream = fs.createReadStream(dirname + '/sample.txt'); + const rl = readline.createInterface({ + input: fileStream, + crlfDelay: Infinity + }); + const matchingLines = []; + rl.on('line', (line) => { + if (line.includes('Fusce')) { + matchingLines.push(line); + } + }); + rl.on('close', () => { + resolve(matchingLines); + }); + rl.on('error', (err) => { + reject(err); + }); + }); + }; + + readFileLineByLine() + .then(lines => { + res.writeHead(200, { 'Content-Type': 'application/json' }); + res.end(JSON.stringify(lines)); + }) + .catch(err => { + res.writeHead(500, { 'Content-Type': 'text/plain' }); + res.end('Error reading file'); + }); +} + +module.exports = handleGetLineByLineFromTextFile; diff --git a/exercisefiles/node/endpoints/health.js b/exercisefiles/node/endpoints/health.js new file mode 100644 index 0000000..220d86c --- /dev/null +++ b/exercisefiles/node/endpoints/health.js @@ -0,0 +1,10 @@ +/** + * GET /health or /healthz + * Simple liveness probe; returns 'ok' when the server is running + */ +function handleHealth(query, res) { + res.writeHead(200, { 'Content-Type': 'text/plain' }); + res.end('ok'); +} + +module.exports = handleHealth; diff --git a/exercisefiles/node/endpoints/moviesByDirector.js b/exercisefiles/node/endpoints/moviesByDirector.js new file mode 100644 index 0000000..385b073 --- /dev/null +++ b/exercisefiles/node/endpoints/moviesByDirector.js @@ -0,0 +1,40 @@ +const axios = require('axios'); + +/** + * GET /MoviesByDirector?director=NAME + * Uses OMDb API to search and filter movies by director + */ +function handleMoviesByDirector(query, res, omdbApiKey) { + const director = query.director; + if (director) { + // Search for movies, then filter by director + axios.get(`http://www.omdbapi.com/?apikey=${omdbApiKey}&s=${encodeURIComponent(director)}&type=movie`) + .then(async response => { + if (response.data.Response === 'True') { + // Get detailed info for each movie to verify director + const moviePromises = response.data.Search.map(movie => + axios.get(`http://www.omdbapi.com/?apikey=${omdbApiKey}&i=${movie.imdbID}`) + ); + const movieDetails = await Promise.all(moviePromises); + const directorMovies = movieDetails + .map(m => m.data) + .filter(m => m.Director && m.Director.toLowerCase().includes(director.toLowerCase())); + + res.writeHead(200, { 'Content-Type': 'application/json' }); + res.end(JSON.stringify(directorMovies)); + } else { + res.writeHead(404, { 'Content-Type': 'application/json' }); + res.end(JSON.stringify({ error: 'No movies found' })); + } + }) + .catch(error => { + res.writeHead(500, { 'Content-Type': 'text/plain' }); + res.end('Error fetching movies'); + }); + } else { + res.writeHead(400, { 'Content-Type': 'text/plain' }); + res.end('director parameter is required'); + } +} + +module.exports = handleMoviesByDirector; diff --git a/exercisefiles/node/endpoints/parseUrl.js b/exercisefiles/node/endpoints/parseUrl.js new file mode 100644 index 0000000..e8b6868 --- /dev/null +++ b/exercisefiles/node/endpoints/parseUrl.js @@ -0,0 +1,22 @@ +/** + * GET /ParseUrl?someurl=ENCODED_URL + * Parses provided URL and returns host component + */ +function handleParseUrl(query, res) { + const someurl = query.someurl; + if (someurl) { + try { + const parsedSomeUrl = new URL(someurl); + res.writeHead(200, { 'Content-Type': 'text/plain' }); + res.end(parsedSomeUrl.host); + } catch (error) { + res.writeHead(400, { 'Content-Type': 'text/plain' }); + res.end('invalid url'); + } + } else { + res.writeHead(400, { 'Content-Type': 'text/plain' }); + res.end('someurl parameter is required'); + } +} + +module.exports = handleParseUrl; diff --git a/exercisefiles/node/endpoints/randomEuropeanCountry.js b/exercisefiles/node/endpoints/randomEuropeanCountry.js new file mode 100644 index 0000000..80feebb --- /dev/null +++ b/exercisefiles/node/endpoints/randomEuropeanCountry.js @@ -0,0 +1,61 @@ +// Static reference data for /RandomEuropeanCountry +const europeanCountries = [ + { country: 'Albania', isoCode: 'AL' }, + { country: 'Andorra', isoCode: 'AD' }, + { country: 'Austria', isoCode: 'AT' }, + { country: 'Belarus', isoCode: 'BY' }, + { country: 'Belgium', isoCode: 'BE' }, + { country: 'Bosnia and Herzegovina', isoCode: 'BA' }, + { country: 'Bulgaria', isoCode: 'BG' }, + { country: 'Croatia', isoCode: 'HR' }, + { country: 'Cyprus', isoCode: 'CY' }, + { country: 'Czech Republic', isoCode: 'CZ' }, + { country: 'Denmark', isoCode: 'DK' }, + { country: 'Estonia', isoCode: 'EE' }, + { country: 'Finland', isoCode: 'FI' }, + { country: 'France', isoCode: 'FR' }, + { country: 'Germany', isoCode: 'DE' }, + { country: 'Greece', isoCode: 'GR' }, + { country: 'Hungary', isoCode: 'HU' }, + { country: 'Iceland', isoCode: 'IS' }, + { country: 'Ireland', isoCode: 'IE' }, + { country: 'Italy', isoCode: 'IT' }, + { country: 'Latvia', isoCode: 'LV' }, + { country: 'Liechtenstein', isoCode: 'LI' }, + { country: 'Lithuania', isoCode: 'LT' }, + { country: 'Luxembourg', isoCode: 'LU' }, + { country: 'Malta', isoCode: 'MT' }, + { country: 'Moldova', isoCode: 'MD' }, + { country: 'Monaco', isoCode: 'MC' }, + { country: 'Montenegro', isoCode: 'ME' }, + { country: 'Netherlands', isoCode: 'NL' }, + { country: 'North Macedonia', isoCode: 'MK' }, + { country: 'Norway', isoCode: 'NO' }, + { country: 'Poland', isoCode: 'PL' }, + { country: 'Portugal', isoCode: 'PT' }, + { country: 'Romania', isoCode: 'RO' }, + { country: 'Russia', isoCode: 'RU' }, + { country: 'San Marino', isoCode: 'SM' }, + { country: 'Serbia', isoCode: 'RS' }, + { country: 'Slovakia', isoCode: 'SK' }, + { country: 'Slovenia', isoCode: 'SI' }, + { country: 'Spain', isoCode: 'ES' }, + { country: 'Sweden', isoCode: 'SE' }, + { country: 'Switzerland', isoCode: 'CH' }, + { country: 'Ukraine', isoCode: 'UA' }, + { country: 'United Kingdom', isoCode: 'GB' }, + { country: 'Vatican City', isoCode: 'VA' } +]; + +/** + * GET /RandomEuropeanCountry + * Returns a random country from static list + */ +function handleRandomEuropeanCountry(query, res) { + const randomIndex = Math.floor(Math.random() * europeanCountries.length); + const randomCountry = europeanCountries[randomIndex]; + res.writeHead(200, { 'Content-Type': 'application/json' }); + res.end(JSON.stringify(randomCountry)); +} + +module.exports = handleRandomEuropeanCountry; diff --git a/exercisefiles/node/endpoints/returnColorCode.js b/exercisefiles/node/endpoints/returnColorCode.js new file mode 100644 index 0000000..bf62028 --- /dev/null +++ b/exercisefiles/node/endpoints/returnColorCode.js @@ -0,0 +1,32 @@ +const fs = require('fs'); + +/** + * GET /ReturnColorCode?color=NAME + * Looks up color hex code from colors.json + */ +function handleReturnColorCode(query, res, dirname) { + const color = query.color; + if (color) { + fs.readFile(dirname + '/colors.json', 'utf8', (err, data) => { + if (err) { + res.writeHead(500, { 'Content-Type': 'text/plain' }); + res.end('Error reading colors file'); + return; + } + const colors = JSON.parse(data); + const foundColor = colors.find(c => c.color.toLowerCase() === color.toLowerCase()); + if (foundColor) { + res.writeHead(200, { 'Content-Type': 'text/plain' }); + res.end(foundColor.code.hex); + } else { + res.writeHead(404, { 'Content-Type': 'text/plain' }); + res.end('color not found'); + } + }); + } else { + res.writeHead(400, { 'Content-Type': 'text/plain' }); + res.end('color parameter is required'); + } +} + +module.exports = handleReturnColorCode; diff --git a/exercisefiles/node/endpoints/tellMeAJoke.js b/exercisefiles/node/endpoints/tellMeAJoke.js new file mode 100644 index 0000000..2ab22a5 --- /dev/null +++ b/exercisefiles/node/endpoints/tellMeAJoke.js @@ -0,0 +1,23 @@ +const axios = require('axios'); + +/** + * GET /TellMeAJoke + * Fetches a random joke from public API + */ +function handleTellMeAJoke(query, res) { + axios.get('https://official-joke-api.appspot.com/random_joke') + .then(response => { + const joke = response.data; + res.writeHead(200, { 'Content-Type': 'application/json' }); + res.end(JSON.stringify({ + setup: joke.setup, + punchline: joke.punchline + })); + }) + .catch(error => { + res.writeHead(500, { 'Content-Type': 'text/plain' }); + res.end('Error fetching joke'); + }); +} + +module.exports = handleTellMeAJoke; diff --git a/exercisefiles/node/endpoints/validatePhoneNumber.js b/exercisefiles/node/endpoints/validatePhoneNumber.js new file mode 100644 index 0000000..332375a --- /dev/null +++ b/exercisefiles/node/endpoints/validatePhoneNumber.js @@ -0,0 +1,23 @@ +/** + * GET /Validatephonenumber?phoneNumber=+34######### + * Validates Spanish phone numbers (+34 followed by 9 digits) + */ +function handleValidatePhoneNumber(query, res) { + const phoneNumber = query.phoneNumber; + if (phoneNumber) { + // Spanish phone number format: +34 followed by 9 digits + const spanishPhoneRegex = /^\+34[0-9]{9}$/; + if (spanishPhoneRegex.test(phoneNumber)) { + res.writeHead(200, { 'Content-Type': 'text/plain' }); + res.end('valid'); + } else { + res.writeHead(200, { 'Content-Type': 'text/plain' }); + res.end('invalid'); + } + } else { + res.writeHead(400, { 'Content-Type': 'text/plain' }); + res.end('phoneNumber parameter is required'); + } +} + +module.exports = handleValidatePhoneNumber; diff --git a/exercisefiles/node/endpoints/validateSpanishDNI.js b/exercisefiles/node/endpoints/validateSpanishDNI.js new file mode 100644 index 0000000..f7cbfa6 --- /dev/null +++ b/exercisefiles/node/endpoints/validateSpanishDNI.js @@ -0,0 +1,32 @@ +/** + * GET /ValidateSpanishDNI?dni=########X + * Validates Spanish DNI checksum letter + */ +function handleValidateSpanishDNI(query, res) { + const dni = query.dni; + if (dni) { + const dniRegex = /^(\d{8})([A-Z])$/i; + const letters = 'TRWAGMYFPDXBNJZSQVHLCKE'; + const match = dni.match(dniRegex); + if (match) { + const number = parseInt(match[1], 10); + const letter = match[2].toUpperCase(); + const correctLetter = letters[number % 23]; + if (letter === correctLetter) { + res.writeHead(200, { 'Content-Type': 'text/plain' }); + res.end('valid'); + } else { + res.writeHead(200, { 'Content-Type': 'text/plain' }); + res.end('invalid'); + } + } else { + res.writeHead(200, { 'Content-Type': 'text/plain' }); + res.end('invalid'); + } + } else { + res.writeHead(400, { 'Content-Type': 'text/plain' }); + res.end('dni parameter is required'); + } +} + +module.exports = handleValidateSpanishDNI; diff --git a/exercisefiles/node/nodeserver.js b/exercisefiles/node/nodeserver.js index bc2a4d6..76dd40e 100644 --- a/exercisefiles/node/nodeserver.js +++ b/exercisefiles/node/nodeserver.js @@ -49,61 +49,25 @@ // Node core/external deps used by various endpoints const http = require('http'); // HTTP server const url = require('url'); // URL parsing (query/pathname) -const fs = require('fs'); // File I/O for colors.json/sample.txt -const axios = require('axios'); // HTTP client for external APIs + +// Import endpoint handlers +const handleGet = require('./endpoints/get'); +const handleDaysBetweenDates = require('./endpoints/daysBetweenDates'); +const handleValidatePhoneNumber = require('./endpoints/validatePhoneNumber'); +const handleValidateSpanishDNI = require('./endpoints/validateSpanishDNI'); +const handleReturnColorCode = require('./endpoints/returnColorCode'); +const handleTellMeAJoke = require('./endpoints/tellMeAJoke'); +const handleMoviesByDirector = require('./endpoints/moviesByDirector'); +const handleParseUrl = require('./endpoints/parseUrl'); +const handleGetFullTextFile = require('./endpoints/getFullTextFile'); +const handleGetLineByLineFromTextFile = require('./endpoints/getLineByLineFromTextFile'); +const handleCalculateMemoryConsumption = require('./endpoints/calculateMemoryConsumption'); +const handleRandomEuropeanCountry = require('./endpoints/randomEuropeanCountry'); +const handleHealth = require('./endpoints/health'); // OMDb API key placeholder for /MoviesByDirector. Replace or inject via env. const OMDB_API_KEY = 'YOUR_API_KEY'; // Replace with your actual API key -// Static reference data for /RandomEuropeanCountry -const europeanCountries = [ - { country: 'Albania', isoCode: 'AL' }, - { country: 'Andorra', isoCode: 'AD' }, - { country: 'Austria', isoCode: 'AT' }, - { country: 'Belarus', isoCode: 'BY' }, - { country: 'Belgium', isoCode: 'BE' }, - { country: 'Bosnia and Herzegovina', isoCode: 'BA' }, - { country: 'Bulgaria', isoCode: 'BG' }, - { country: 'Croatia', isoCode: 'HR' }, - { country: 'Cyprus', isoCode: 'CY' }, - { country: 'Czech Republic', isoCode: 'CZ' }, - { country: 'Denmark', isoCode: 'DK' }, - { country: 'Estonia', isoCode: 'EE' }, - { country: 'Finland', isoCode: 'FI' }, - { country: 'France', isoCode: 'FR' }, - { country: 'Germany', isoCode: 'DE' }, - { country: 'Greece', isoCode: 'GR' }, - { country: 'Hungary', isoCode: 'HU' }, - { country: 'Iceland', isoCode: 'IS' }, - { country: 'Ireland', isoCode: 'IE' }, - { country: 'Italy', isoCode: 'IT' }, - { country: 'Latvia', isoCode: 'LV' }, - { country: 'Liechtenstein', isoCode: 'LI' }, - { country: 'Lithuania', isoCode: 'LT' }, - { country: 'Luxembourg', isoCode: 'LU' }, - { country: 'Malta', isoCode: 'MT' }, - { country: 'Moldova', isoCode: 'MD' }, - { country: 'Monaco', isoCode: 'MC' }, - { country: 'Montenegro', isoCode: 'ME' }, - { country: 'Netherlands', isoCode: 'NL' }, - { country: 'North Macedonia', isoCode: 'MK' }, - { country: 'Norway', isoCode: 'NO' }, - { country: 'Poland', isoCode: 'PL' }, - { country: 'Portugal', isoCode: 'PT' }, - { country: 'Romania', isoCode: 'RO' }, - { country: 'Russia', isoCode: 'RU' }, - { country: 'San Marino', isoCode: 'SM' }, - { country: 'Serbia', isoCode: 'RS' }, - { country: 'Slovakia', isoCode: 'SK' }, - { country: 'Slovenia', isoCode: 'SI' }, - { country: 'Spain', isoCode: 'ES' }, - { country: 'Sweden', isoCode: 'SE' }, - { country: 'Switzerland', isoCode: 'CH' }, - { country: 'Ukraine', isoCode: 'UA' }, - { country: 'United Kingdom', isoCode: 'GB' }, - { country: 'Vatican City', isoCode: 'VA' } -]; - const server = http.createServer((req, res) => { // Parse pathname and query parameters from the request URL const parsedUrl = url.parse(req.url, true); @@ -112,244 +76,55 @@ const server = http.createServer((req, res) => { // /get: echo 'hello ' if key provided if (pathname === '/get') { - const key = query.key; - if (key) { - res.writeHead(200, { 'Content-Type': 'text/plain' }); - res.end('hello ' + key); - } else { - res.writeHead(400, { 'Content-Type': 'text/plain' }); - res.end('key not passed'); - } + handleGet(query, res); } // /DaysBetweenDates: compute absolute difference in days between two dates else if (pathname === '/DaysBetweenDates') { - const date1 = query.date1; - const date2 = query.date2; - if (date1 && date2) { - const d1 = new Date(date1); - const d2 = new Date(date2); - const diffTime = Math.abs(d2 - d1); - const diffDays = Math.ceil(diffTime / (1000 * 60 * 60 * 24)); - res.writeHead(200, { 'Content-Type': 'text/plain' }); - res.end(String(diffDays)); - } else { - res.writeHead(400, { 'Content-Type': 'text/plain' }); - res.end('date1 and date2 parameters are required'); - } + handleDaysBetweenDates(query, res); } // /Validatephonenumber: validate Spanish numbers (+34 + 9 digits) else if (pathname === '/Validatephonenumber') { - const phoneNumber = query.phoneNumber; - if (phoneNumber) { - // Spanish phone number format: +34 followed by 9 digits - const spanishPhoneRegex = /^\+34[0-9]{9}$/; - if (spanishPhoneRegex.test(phoneNumber)) { - res.writeHead(200, { 'Content-Type': 'text/plain' }); - res.end('valid'); - } else { - res.writeHead(200, { 'Content-Type': 'text/plain' }); - res.end('invalid'); - } - } else { - res.writeHead(400, { 'Content-Type': 'text/plain' }); - res.end('phoneNumber parameter is required'); - } + handleValidatePhoneNumber(query, res); } // /ValidateSpanishDNI: validate DNI checksum letter else if (pathname === '/ValidateSpanishDNI') { - const dni = query.dni; - if (dni) { - const dniRegex = /^(\d{8})([A-Z])$/i; - const letters = 'TRWAGMYFPDXBNJZSQVHLCKE'; - const match = dni.match(dniRegex); - if (match) { - const number = parseInt(match[1], 10); - const letter = match[2].toUpperCase(); - const correctLetter = letters[number % 23]; - if (letter === correctLetter) { - res.writeHead(200, { 'Content-Type': 'text/plain' }); - res.end('valid'); - } else { - res.writeHead(200, { 'Content-Type': 'text/plain' }); - res.end('invalid'); - } - } else { - res.writeHead(200, { 'Content-Type': 'text/plain' }); - res.end('invalid'); - } - } else { - res.writeHead(400, { 'Content-Type': 'text/plain' }); - res.end('dni parameter is required'); - } + handleValidateSpanishDNI(query, res); } // /ReturnColorCode: look up color hex code from colors.json else if (pathname === '/ReturnColorCode') { - const color = query.color; - if (color) { - fs.readFile(__dirname + '/colors.json', 'utf8', (err, data) => { - if (err) { - res.writeHead(500, { 'Content-Type': 'text/plain' }); - res.end('Error reading colors file'); - return; - } - const colors = JSON.parse(data); - const foundColor = colors.find(c => c.color.toLowerCase() === color.toLowerCase()); - if (foundColor) { - res.writeHead(200, { 'Content-Type': 'text/plain' }); - res.end(foundColor.code.hex); - } else { - res.writeHead(404, { 'Content-Type': 'text/plain' }); - res.end('color not found'); - } - }); - } else { - res.writeHead(400, { 'Content-Type': 'text/plain' }); - res.end('color parameter is required'); - } + handleReturnColorCode(query, res, __dirname); } // /TellMeAJoke: fetch random joke from public API else if (pathname === '/TellMeAJoke') { - axios.get('https://official-joke-api.appspot.com/random_joke') - .then(response => { - const joke = response.data; - res.writeHead(200, { 'Content-Type': 'application/json' }); - res.end(JSON.stringify({ - setup: joke.setup, - punchline: joke.punchline - })); - }) - .catch(error => { - res.writeHead(500, { 'Content-Type': 'text/plain' }); - res.end('Error fetching joke'); - }); + handleTellMeAJoke(query, res); } // /MoviesByDirector: OMDb search + detail fetch to filter by director else if (pathname === '/MoviesByDirector') { - const director = query.director; - if (director) { - // Search for movies, then filter by director - axios.get(`http://www.omdbapi.com/?apikey=${OMDB_API_KEY}&s=${encodeURIComponent(director)}&type=movie`) - .then(async response => { - if (response.data.Response === 'True') { - // Get detailed info for each movie to verify director - const moviePromises = response.data.Search.map(movie => - axios.get(`http://www.omdbapi.com/?apikey=${OMDB_API_KEY}&i=${movie.imdbID}`) - ); - const movieDetails = await Promise.all(moviePromises); - const directorMovies = movieDetails - .map(m => m.data) - .filter(m => m.Director && m.Director.toLowerCase().includes(director.toLowerCase())); - - res.writeHead(200, { 'Content-Type': 'application/json' }); - res.end(JSON.stringify(directorMovies)); - } else { - res.writeHead(404, { 'Content-Type': 'application/json' }); - res.end(JSON.stringify({ error: 'No movies found' })); - } - }) - .catch(error => { - res.writeHead(500, { 'Content-Type': 'text/plain' }); - res.end('Error fetching movies'); - }); - } else { - res.writeHead(400, { 'Content-Type': 'text/plain' }); - res.end('director parameter is required'); - } + handleMoviesByDirector(query, res, OMDB_API_KEY); } // /ParseUrl: parse provided URL and return host (domain[:port]) else if (pathname === '/ParseUrl') { - const someurl = query.someurl; - if (someurl) { - try { - const parsedSomeUrl = new URL(someurl); - const result = { - protocol: parsedSomeUrl.protocol, - host: parsedSomeUrl.host, - port: parsedSomeUrl.port || '', - path: parsedSomeUrl.pathname, - querystring: parsedSomeUrl.search, - hash: parsedSomeUrl.hash - }; - res.writeHead(200, { 'Content-Type': 'text/plain' }); - res.end(parsedSomeUrl.host); - } catch (error) { - res.writeHead(400, { 'Content-Type': 'text/plain' }); - res.end('invalid url'); - } - } else { - res.writeHead(400, { 'Content-Type': 'text/plain' }); - res.end('someurl parameter is required'); - } + handleParseUrl(query, res); } // /GetFullTextFile: read entire file and filter lines containing 'Fusce' else if (pathname === '/GetFullTextFile') { - fs.readFile(__dirname + '/sample.txt', 'utf8', (err, data) => { - if (err) { - res.writeHead(500, { 'Content-Type': 'text/plain' }); - res.end('Error reading file'); - return; - } - const lines = data.split('\n').filter(line => line.includes('Fusce')); - res.writeHead(200, { 'Content-Type': 'text/plain' }); - res.end(lines.join('\n')); - }); + handleGetFullTextFile(query, res, __dirname); } // /GetLineByLinefromtTextFile: stream file line-by-line and collect matches else if (pathname === '/GetLineByLinefromtTextFile') { - const readFileLineByLine = () => { - return new Promise((resolve, reject) => { - const fileStream = fs.createReadStream(__dirname + '/sample.txt'); - const rl = readline.createInterface({ - input: fileStream, - crlfDelay: Infinity - }); - const matchingLines = []; - rl.on('line', (line) => { - if (line.includes('Fusce')) { - matchingLines.push(line); - } - }); - rl.on('close', () => { - resolve(matchingLines); - }); - rl.on('error', (err) => { - reject(err); - }); - }); - }; - - readFileLineByLine() - .then(lines => { - res.writeHead(200, { 'Content-Type': 'application/json' }); - res.end(JSON.stringify(lines)); - }) - .catch(err => { - res.writeHead(500, { 'Content-Type': 'text/plain' }); - res.end('Error reading file'); - }); + handleGetLineByLineFromTextFile(query, res, __dirname); } // /CalculateMemoryConsumption: report heap usage in GB else if (pathname === '/CalculateMemoryConsumption') { - const memoryUsage = process.memoryUsage(); - const memoryInGB = (memoryUsage.heapUsed / (1024 * 1024 * 1024)).toFixed(2); - res.writeHead(200, { 'Content-Type': 'text/plain' }); - res.end(memoryInGB); + handleCalculateMemoryConsumption(query, res); } // /RandomEuropeanCountry: return a random country from static list else if (pathname === '/RandomEuropeanCountry') { - const randomIndex = Math.floor(Math.random() * europeanCountries.length); - const randomCountry = europeanCountries[randomIndex]; - res.writeHead(200, { 'Content-Type': 'application/json' }); - res.end(JSON.stringify(randomCountry)); + handleRandomEuropeanCountry(query, res); } // Health-check endpoint else if (pathname === '/health' || pathname === '/healthz') { - /** - * GET /health or /healthz - * Simple liveness probe; returns 'ok' when the server is running. - */ - res.writeHead(200, { 'Content-Type': 'text/plain' }); - res.end('ok'); + handleHealth(query, res); } // Fallback: unmatched path else { diff --git a/exercisefiles/node/package.json b/exercisefiles/node/package.json index 73934db..c865099 100644 --- a/exercisefiles/node/package.json +++ b/exercisefiles/node/package.json @@ -12,5 +12,8 @@ "description": "", "dependencies": { "axios": "^1.13.2" + }, + "devDependencies": { + "mocha": "^11.7.5" } } diff --git a/exercisefiles/node/test.js b/exercisefiles/node/test.js index 8650d1b..6a06a51 100644 --- a/exercisefiles/node/test.js +++ b/exercisefiles/node/test.js @@ -164,6 +164,57 @@ describe('Node Server', () => { }); }); }); + + // Test TellMeAJoke + it('should return a joke with setup and punchline or error', (done) => { + http.get('http://localhost:3000/TellMeAJoke', (res) => { + let data = ''; + res.on('data', (chunk) => { + data += chunk; + }); + res.on('end', () => { + if (res.statusCode === 200) { + const result = JSON.parse(data); + assert.ok(result.setup); + assert.ok(result.punchline); + } else if (res.statusCode === 500) { + assert.equal(data, 'Error fetching joke'); + } + done(); + }); + }); + }); + + // Test GetFullTextFile + it('should return lines containing "Fusce" from sample.txt', (done) => { + http.get('http://localhost:3000/GetFullTextFile', (res) => { + let data = ''; + res.on('data', (chunk) => { + data += chunk; + }); + res.on('end', () => { + assert.ok(data.includes('Fusce')); + done(); + }); + }); + }); + + // Test GetLineByLinefromtTextFile + it('should return JSON array of lines containing "Fusce"', (done) => { + http.get('http://localhost:3000/GetLineByLinefromtTextFile', (res) => { + let data = ''; + res.on('data', (chunk) => { + data += chunk; + }); + res.on('end', () => { + const result = JSON.parse(data); + assert.ok(Array.isArray(result)); + assert.ok(result.length > 0); + assert.ok(result[0].includes('Fusce')); + done(); + }); + }); + }); }); // Exercise 6: Health-check endpoint