diff --git a/assignments/hackyourtemperature/.gitignore b/assignments/hackyourtemperature/.gitignore new file mode 100644 index 000000000..b512c09d4 --- /dev/null +++ b/assignments/hackyourtemperature/.gitignore @@ -0,0 +1 @@ +node_modules \ No newline at end of file diff --git a/assignments/hackyourtemperature/__tests__/app.test.js b/assignments/hackyourtemperature/__tests__/app.test.js new file mode 100644 index 000000000..15dda6d8a --- /dev/null +++ b/assignments/hackyourtemperature/__tests__/app.test.js @@ -0,0 +1,34 @@ +import app from '../app.js'; +import supertest from 'supertest'; + +const request = supertest(app); + +describe('POST /weather', () => { + test('400 when cityName missing', async () => { + const res = await request.post('/weather').send({}); + expect(res.status).toBe(400); + expect(typeof res.body.error).toBe('string'); + expect(res.body.error.toLowerCase()).toContain('cityname'); + }); + + test('404 when city not found', async () => { + const res = await request + .post('/weather') + .send({ cityName: '___nope___' }); + expect(res.status).toBe(404); + expect(typeof res.body.weatherText).toBe('string'); + expect(res.body.weatherText.toLowerCase()).toContain('not found'); + }); + + test('200 for a valid city returns fields', async () => { + const res = await request + .post('/weather') + .send({ cityName: 'Amsterdam' }); + expect(res.status).toBe(200); + expect(res.body.cityName.toLowerCase()).toContain('amsterdam'); + expect(typeof res.body.weatherText).toBe('string'); + expect( + typeof res.body.temperature === 'number' || res.body.temperature === null + ).toBe(true); + }); +}); diff --git a/assignments/hackyourtemperature/app.js b/assignments/hackyourtemperature/app.js new file mode 100644 index 000000000..c399b5703 --- /dev/null +++ b/assignments/hackyourtemperature/app.js @@ -0,0 +1,47 @@ +import express from 'express'; +import fetch from 'node-fetch'; +import keys from './sources/keys.js'; + +const app = express(); +app.use(express.json()); + +app.get('/', (_req, res) => { + res.type('text/plain').send('hello from backend to frontend'); +}); + +app.post('/weather', async (req, res) => { + const { cityName } = req.body ?? {}; + if (!cityName || typeof cityName !== 'string' || !cityName.trim()) { + return res.status(400).json({ + error: 'Send JSON like { "cityName": "Amsterdam" }', + }); + } + + try { + const url = + `https://api.openweathermap.org/data/2.5/weather` + + `?q=${encodeURIComponent(cityName.trim())}` + + `&appid=${keys.API_KEY}&units=metric`; + + const r = await fetch(url); + const data = await r.json(); + + if (r.status === 404 || data?.cod === '404') { + return res.status(404).json({ weatherText: 'City is not found!' }); + } + + const name = data?.name ?? cityName.trim(); + const temp = typeof data?.main?.temp === 'number' ? data.main.temp : null; + + return res.json({ + cityName: name, + temperature: temp, + weatherText: `${name}: ${temp}°C`, + }); + } catch (err) { + console.error(err); + return res.status(500).json({ error: 'Server error contacting API' }); + } +}); + +export default app; diff --git a/assignments/hackyourtemperature/babel.config.cjs b/assignments/hackyourtemperature/babel.config.cjs new file mode 100644 index 000000000..ce799ebfe --- /dev/null +++ b/assignments/hackyourtemperature/babel.config.cjs @@ -0,0 +1,12 @@ +module.exports = { + presets: [ + [ + '@babel/preset-env', + { + targets: { + node: 'current' + }, + }, + ], + ], +}; diff --git a/assignments/hackyourtemperature/jest.config.js b/assignments/hackyourtemperature/jest.config.js new file mode 100644 index 000000000..30abe3b84 --- /dev/null +++ b/assignments/hackyourtemperature/jest.config.js @@ -0,0 +1,8 @@ +export default { + transform: { + '^.+\\.jsx?$': 'babel-jest' + }, + + testEnvironment: 'node', + transformIgnorePatterns: [], +}; diff --git a/assignments/hackyourtemperature/package.json b/assignments/hackyourtemperature/package.json new file mode 100644 index 000000000..236140740 --- /dev/null +++ b/assignments/hackyourtemperature/package.json @@ -0,0 +1,27 @@ +{ + "name": "hackyourtemperature", + "version": "1.0.0", + "type": "module", + "main": "server.js", + "description": "", + "scripts": { + "test": "jest", + "start": "node server.js", + "dev": "nodemon --ext js,json --watch . server.js" + }, + "keywords": [], + "author": "", + "license": "ISC", + "dependencies": { + "express": "^5.1.0", + "express-handlebars": "^8.0.3", + "node-fetch": "^3.3.2" + }, + "devDependencies": { + "@babel/preset-env": "^7.28.5", + "babel-jest": "^30.2.0", + "jest": "^30.2.0", + "nodemon": "^3.1.10", + "supertest": "^7.1.4" + } +} diff --git a/assignments/hackyourtemperature/server.js b/assignments/hackyourtemperature/server.js new file mode 100644 index 000000000..10e751930 --- /dev/null +++ b/assignments/hackyourtemperature/server.js @@ -0,0 +1,6 @@ +import app from './app.js'; + +const PORT = process.env.PORT || 3000; +app.listen(PORT, () => { + console.log(`Server listening on http://localhost:${PORT}`); +}); diff --git a/assignments/hackyourtemperature/sources/keys.js b/assignments/hackyourtemperature/sources/keys.js new file mode 100644 index 000000000..4b25a3c17 --- /dev/null +++ b/assignments/hackyourtemperature/sources/keys.js @@ -0,0 +1,3 @@ +export default { + API_KEY: 'f6f12ccf08763d2f26c3ea0142cc94ee', +};