From 79d942faffb84a199afa4f9992b86ee1d061e4d2 Mon Sep 17 00:00:00 2001 From: Majd Hamde Date: Sun, 26 Oct 2025 18:30:13 +0100 Subject: [PATCH 1/5] majd-w1-node --- week1/prep-exercises/1-web-server/server.js | 41 ++++++++++++++++++--- 1 file changed, 35 insertions(+), 6 deletions(-) diff --git a/week1/prep-exercises/1-web-server/server.js b/week1/prep-exercises/1-web-server/server.js index 90cb5ee65..d449f4e29 100644 --- a/week1/prep-exercises/1-web-server/server.js +++ b/week1/prep-exercises/1-web-server/server.js @@ -3,12 +3,41 @@ */ const http = require('http'); +const fs = require('fs'); +const path = require('path'); -//create a server -let server = http.createServer(function (req, res) { - // YOUR CODE GOES IN HERE - res.write('Hello World!'); // Sends a response back to the client - res.end(); // Ends the response +// Create the server +const server = http.createServer((req, res) => { + if (req.url === '/') { + // Serve the index.html file + fs.readFile(path.join(__dirname, 'index.html'), (err, data) => { + if (err) { + res.writeHead(500, { 'Content-Type': 'text/plain' }); + res.end('Server Error'); + } else { + res.writeHead(200, { 'Content-Type': 'text/html' }); + res.end(data); + } + }); + } else if (req.url === '/index.js') { + // Serve the index.js file + fs.readFile(path.join(__dirname, 'index.js'), (err, data) => { + if (err) { + res.writeHead(404, { 'Content-Type': 'text/plain' }); + res.end('File Not Found'); + } else { + res.writeHead(200, { 'Content-Type': 'application/javascript' }); + res.end(data); + } + }); + } else { + // Handle any other request with 404 + res.writeHead(404, { 'Content-Type': 'text/plain' }); + res.end('Not Found'); + } }); -server.listen(3000); // The server starts to listen on port 3000 +// Start listening on port 3000 +server.listen(3000, () => { + console.log('✅ Server running at http://localhost:3000'); +}); From bdd658f1c0d01c1c199a9f03bb035424519eb3a5 Mon Sep 17 00:00:00 2001 From: Majd Hamde Date: Tue, 28 Oct 2025 17:08:43 +0100 Subject: [PATCH 2/5] majd-w1-assigment --- assignments/HackYourTemperature/package.json | 22 ++++++++++++ assignments/HackYourTemperature/server.js | 37 ++++++++++++++++++++ 2 files changed, 59 insertions(+) create mode 100644 assignments/HackYourTemperature/package.json create mode 100644 assignments/HackYourTemperature/server.js diff --git a/assignments/HackYourTemperature/package.json b/assignments/HackYourTemperature/package.json new file mode 100644 index 000000000..6bd97d48f --- /dev/null +++ b/assignments/HackYourTemperature/package.json @@ -0,0 +1,22 @@ +{ + "name": "hackyourtemperature", + "version": "1.0.0", + "description": "HackYourTemperature project - Week 1 setup", + "main": "server.js", + "type": "module", + "scripts": { + "start": "node server.js", + "dev": "nodemon server.js" + }, + "keywords": [], + "author": "Majd Jad_alhaq", + "license": "ISC", + "dependencies": { + "express": "^4.21.1", + "express-handlebars": "^8.0.3", + "node-fetch": "^3.3.2" + }, + "devDependencies": { + "nodemon": "^3.1.7" + } +} diff --git a/assignments/HackYourTemperature/server.js b/assignments/HackYourTemperature/server.js new file mode 100644 index 000000000..571a3df42 --- /dev/null +++ b/assignments/HackYourTemperature/server.js @@ -0,0 +1,37 @@ +// server.js + +// Import necessary packages (using ES modules) +import express from "express"; +import fetch from "node-fetch"; // not used yet, but needed later +import { engine } from "express-handlebars"; + +// Create an Express application +const app = express(); + +// Middlewares +app.use(express.json()); // allows Express to read JSON in POST requests + +// Optional: set up handlebars for later weeks +app.engine("handlebars", engine()); +app.set("view engine", "handlebars"); +app.set("views", "./views"); + +// GET route (homepage) +app.get("/", (req, res) => { + res.send("hello from backend to frontend!"); +}); + +// POST route +app.post("/weather", (req, res) => { + // Extract cityName from the JSON body + const { cityName } = req.body; + + // Send it back to the client + res.json({ message: `You sent: ${cityName}` }); +}); + +// Listen to port 3000 +const PORT = 3000; +app.listen(PORT, () => { + console.log(`Server is running on http://localhost:${PORT}`); +}); From 2b1b21ad4140adb52ef6f22de223b709fe97f5bc Mon Sep 17 00:00:00 2001 From: Majd Hamde Date: Sun, 2 Nov 2025 13:29:11 +0100 Subject: [PATCH 3/5] majd-w2-node.js --- .../1-blog-API/blogs/My first blog | 1 + week2/prep-exercises/1-blog-API/package.json | 2 +- week2/prep-exercises/1-blog-API/server.js | 101 ++++++++++++++++-- 3 files changed, 94 insertions(+), 10 deletions(-) create mode 100644 week2/prep-exercises/1-blog-API/blogs/My first blog diff --git a/week2/prep-exercises/1-blog-API/blogs/My first blog b/week2/prep-exercises/1-blog-API/blogs/My first blog new file mode 100644 index 000000000..e5d353447 --- /dev/null +++ b/week2/prep-exercises/1-blog-API/blogs/My first blog @@ -0,0 +1 @@ +Lorem ipsum \ No newline at end of file diff --git a/week2/prep-exercises/1-blog-API/package.json b/week2/prep-exercises/1-blog-API/package.json index d89c4bd76..148b0796a 100644 --- a/week2/prep-exercises/1-blog-API/package.json +++ b/week2/prep-exercises/1-blog-API/package.json @@ -10,6 +10,6 @@ "author": "", "license": "ISC", "dependencies": { - "express": "^4.17.1" + "express": "^4.21.2" } } diff --git a/week2/prep-exercises/1-blog-API/server.js b/week2/prep-exercises/1-blog-API/server.js index 3f615e8f5..7775ec4d8 100644 --- a/week2/prep-exercises/1-blog-API/server.js +++ b/week2/prep-exercises/1-blog-API/server.js @@ -1,10 +1,93 @@ -const express = require('express') +const express = require("express"); +const fs = require("fs"); +const path = require("path"); + const app = express(); - - -// YOUR CODE GOES IN HERE -app.get('/', function (req, res) { - res.send('Hello World') -}) - -app.listen(3000) \ No newline at end of file +app.use(express.json()); // Middleware to parse JSON bodies + +// Folder where all blogs will be stored +const BLOGS_DIR = path.join(__dirname, "blogs"); + +// Make sure the folder exists +if (!fs.existsSync(BLOGS_DIR)) { + fs.mkdirSync(BLOGS_DIR); +} + +// Create a new blog post +app.post("/blogs", (req, res) => { + const { title, content } = req.body; + + if (!title || !content) { + return res.status(400).send("Title and content are required!"); + } + + const filePath = path.join(BLOGS_DIR, title); + + if (fs.existsSync(filePath)) { + return res.status(400).send("Post already exists!"); + } + + fs.writeFileSync(filePath, content); + res.status(201).send("ok"); +}); + +// Read a single blog post +app.get("/blogs/:title", (req, res) => { + const title = req.params.title; + const filePath = path.join(BLOGS_DIR, title); + + if (!fs.existsSync(filePath)) { + return res.status(404).send("This post does not exist!"); + } + + const content = fs.readFileSync(filePath, "utf8"); + res.status(200).send(content); +}); + +// Read all posts (titles only) +app.get("/blogs", (req, res) => { + const files = fs.readdirSync(BLOGS_DIR); + const titles = files.map((file) => ({ title: file })); + res.status(200).json(titles); +}); + +// Update an existing post +app.put("/blogs/:title", (req, res) => { + const title = req.params.title; + const { content } = req.body; + const filePath = path.join(BLOGS_DIR, title); + + if (!fs.existsSync(filePath)) { + return res.status(404).send("This post does not exist!"); + } + + if (!content) { + return res.status(400).send("Content is required!"); + } + + fs.writeFileSync(filePath, content); + res.status(200).send("ok"); +}); + +// Delete a post +app.delete("/blogs/:title", (req, res) => { + const title = req.params.title; + const filePath = path.join(BLOGS_DIR, title); + + if (!fs.existsSync(filePath)) { + return res.status(404).send("This post does not exist!"); + } + + fs.unlinkSync(filePath); + res.status(200).send("ok"); +}); + +// Default root +app.get("/", (req, res) => { + res.send("Hello World - Blog API running"); +}); + +// Start server +app.listen(3000, () => { + console.log("✅ Server running at http://localhost:3000"); +}); From 4d67153386fd7e69f2e450ea373496e713f63f86 Mon Sep 17 00:00:00 2001 From: Majd Hamde Date: Sun, 2 Nov 2025 14:06:27 +0100 Subject: [PATCH 4/5] Week 2 HackYourTemperature: connected OpenWeather API and added Jest tests --- .../HackYourTemperature/__tests__/app.test.js | 35 ++++++++++++++ assignments/HackYourTemperature/app.js | 46 +++++++++++++++++++ .../HackYourTemperature/babel.config.cjs | 4 ++ .../HackYourTemperature/jest.config.js | 12 +++++ assignments/HackYourTemperature/package.json | 15 ++++-- assignments/HackYourTemperature/server.js | 34 +------------- .../HackYourTemperature/sources/keys.js | 7 +++ 7 files changed, 116 insertions(+), 37 deletions(-) create mode 100644 assignments/HackYourTemperature/__tests__/app.test.js create mode 100644 assignments/HackYourTemperature/app.js create mode 100644 assignments/HackYourTemperature/babel.config.cjs create mode 100644 assignments/HackYourTemperature/jest.config.js create mode 100644 assignments/HackYourTemperature/sources/keys.js diff --git a/assignments/HackYourTemperature/__tests__/app.test.js b/assignments/HackYourTemperature/__tests__/app.test.js new file mode 100644 index 000000000..42d5c89be --- /dev/null +++ b/assignments/HackYourTemperature/__tests__/app.test.js @@ -0,0 +1,35 @@ +import app from "../app.js"; +import supertest from "supertest"; + +const request = supertest(app); + +describe("HackYourTemperature API", () => { + it("Quick test", () => { + expect(1).toBe(1); + }); + + it("GET / should return hello", async () => { + const res = await request.get("/"); + expect(res.statusCode).toBe(200); + expect(res.text).toContain("hello"); + }); + + it("POST /weather without cityName should return 400", async () => { + const res = await request.post("/weather").send({}); + expect(res.statusCode).toBe(400); + expect(res.body.weatherText).toContain("required"); + }); + + it("POST /weather with invalid city should return 404", async () => { + const res = await request.post("/weather").send({ cityName: "xyzcity" }); + expect(res.statusCode).toBe(404); + expect(res.body.weatherText).toContain("not found"); + }); + + it("POST /weather with real city should return temperature", async () => { + const res = await request.post("/weather").send({ cityName: "Amsterdam" }); + expect(res.statusCode).toBe(200); + expect(res.body.weatherText).toContain("Amsterdam"); + expect(res.body.weatherText).toContain("°C"); + }); +}); diff --git a/assignments/HackYourTemperature/app.js b/assignments/HackYourTemperature/app.js new file mode 100644 index 000000000..0fc25c06a --- /dev/null +++ b/assignments/HackYourTemperature/app.js @@ -0,0 +1,46 @@ +// app.js +import express from "express"; +import fetch from "node-fetch"; +import keys from "./sources/keys.js"; + +const app = express(); +app.use(express.json()); + +// GET / +app.get("/", (req, res) => { + res.send("hello from backend to frontend!"); +}); + +// POST /weather +app.post("/weather", async (req, res) => { + const { cityName } = req.body; + + // 1️⃣ Validate input + if (!cityName) { + return res.status(400).json({ weatherText: "City name is required" }); + } + + try { + // 2️⃣ Fetch data from OpenWeather + const response = await fetch( + `https://api.openweathermap.org/data/2.5/weather?q=${cityName}&appid=${keys.API_KEY}&units=metric` + ); + const data = await response.json(); + + // 3️⃣ Handle city not found + if (data.cod === "404" || data.message === "city not found") { + return res.status(404).json({ weatherText: "City is not found!" }); + } + + // 4️⃣ Send temperature + const temperature = data.main.temp; + res.json({ + weatherText: `The temperature in ${cityName} is ${temperature}°C` + }); + } catch (error) { + console.error(error); + res.status(500).json({ weatherText: "Something went wrong!" }); + } +}); + +export default app; diff --git a/assignments/HackYourTemperature/babel.config.cjs b/assignments/HackYourTemperature/babel.config.cjs new file mode 100644 index 000000000..0d0b97369 --- /dev/null +++ b/assignments/HackYourTemperature/babel.config.cjs @@ -0,0 +1,4 @@ +// babel.config.cjs +module.exports = { + presets: [["@babel/preset-env", { targets: { node: "current" } }]] + }; \ No newline at end of file diff --git a/assignments/HackYourTemperature/jest.config.js b/assignments/HackYourTemperature/jest.config.js new file mode 100644 index 000000000..6279f1d91 --- /dev/null +++ b/assignments/HackYourTemperature/jest.config.js @@ -0,0 +1,12 @@ +// jest.config.js +export default { + transform: { + "^.+\\.js$": "babel-jest" + }, + testEnvironment: "node", + transformIgnorePatterns: [ + //Transform all ESM modules we depend on + "node_modules/(?!(node-fetch|data-uri-to-buffer|fetch-blob|formdata-polyfill)/)" + ] + }; + \ No newline at end of file diff --git a/assignments/HackYourTemperature/package.json b/assignments/HackYourTemperature/package.json index 6bd97d48f..194537672 100644 --- a/assignments/HackYourTemperature/package.json +++ b/assignments/HackYourTemperature/package.json @@ -4,10 +4,11 @@ "description": "HackYourTemperature project - Week 1 setup", "main": "server.js", "type": "module", - "scripts": { - "start": "node server.js", - "dev": "nodemon server.js" - }, + "scripts": { + "start": "node server.js", + "dev": "nodemon server.js", + "test": "jest" +}, "keywords": [], "author": "Majd Jad_alhaq", "license": "ISC", @@ -17,6 +18,10 @@ "node-fetch": "^3.3.2" }, "devDependencies": { - "nodemon": "^3.1.7" + "@babel/preset-env": "^7.28.5", + "babel-jest": "^30.2.0", + "jest": "^30.2.0", + "nodemon": "^3.1.7", + "supertest": "^7.1.4" } } diff --git a/assignments/HackYourTemperature/server.js b/assignments/HackYourTemperature/server.js index 571a3df42..ff52bffa1 100644 --- a/assignments/HackYourTemperature/server.js +++ b/assignments/HackYourTemperature/server.js @@ -1,37 +1,7 @@ // server.js +import app from "./app.js"; -// Import necessary packages (using ES modules) -import express from "express"; -import fetch from "node-fetch"; // not used yet, but needed later -import { engine } from "express-handlebars"; - -// Create an Express application -const app = express(); - -// Middlewares -app.use(express.json()); // allows Express to read JSON in POST requests - -// Optional: set up handlebars for later weeks -app.engine("handlebars", engine()); -app.set("view engine", "handlebars"); -app.set("views", "./views"); - -// GET route (homepage) -app.get("/", (req, res) => { - res.send("hello from backend to frontend!"); -}); - -// POST route -app.post("/weather", (req, res) => { - // Extract cityName from the JSON body - const { cityName } = req.body; - - // Send it back to the client - res.json({ message: `You sent: ${cityName}` }); -}); - -// Listen to port 3000 const PORT = 3000; app.listen(PORT, () => { - console.log(`Server is running on http://localhost:${PORT}`); + console.log(`Server running on http://localhost:${PORT}`); }); diff --git a/assignments/HackYourTemperature/sources/keys.js b/assignments/HackYourTemperature/sources/keys.js new file mode 100644 index 000000000..c1f3fef19 --- /dev/null +++ b/assignments/HackYourTemperature/sources/keys.js @@ -0,0 +1,7 @@ +// sources/keys.js +const keys = { + API_KEY: "fe2fd877137b6c61f6314ea9c69e8460" + }; + + export default keys; + \ No newline at end of file From dd7b4ed26ccfdff1e2c4e147def8abc4125e5921 Mon Sep 17 00:00:00 2001 From: Majd Hamde Date: Wed, 5 Nov 2025 09:38:19 +0100 Subject: [PATCH 5/5] Week 2 HackYourTemperatur --- assignments/HackYourTemperature/app.js | 33 +++++++++++++++++++------- 1 file changed, 24 insertions(+), 9 deletions(-) diff --git a/assignments/HackYourTemperature/app.js b/assignments/HackYourTemperature/app.js index 0fc25c06a..f491289fe 100644 --- a/assignments/HackYourTemperature/app.js +++ b/assignments/HackYourTemperature/app.js @@ -1,43 +1,58 @@ // app.js -import express from "express"; -import fetch from "node-fetch"; -import keys from "./sources/keys.js"; +// Purpose: Express application for HackYourTemperature API (Weeks 1 & 2 requirements) +// - Loads express, express-handlebars (templating engine), and node-fetch +// - Exposes GET / and POST /weather endpoints +// - Exported app is used by server.js and tests + +import express from "express"; // Web framework +import { engine } from "express-handlebars"; // Templating engine (Week 1: load module) +import fetch from "node-fetch"; // HTTP client for backend -> external API (Week 2) +import keys from "./sources/keys.js"; // Contains OpenWeather API key (Week 2) const app = express(); + +// Enable parsing of JSON request bodies (Week 1: express.json()) app.use(express.json()); -// GET / +// Configure Handlebars view engine (Week 1: load and set up express-handlebars) +// Note: We configure the engine to satisfy the requirement; routes may still send plain text/JSON +app.engine("handlebars", engine()); +app.set("view engine", "handlebars"); +app.set("views", "./views"); + +// GET / (Week 1): Respond with a simple message app.get("/", (req, res) => { res.send("hello from backend to frontend!"); }); -// POST /weather +// POST /weather (Week 2): Accepts JSON body { cityName } and returns weather text app.post("/weather", async (req, res) => { const { cityName } = req.body; - // 1️⃣ Validate input + // Validate input: cityName is required if (!cityName) { return res.status(400).json({ weatherText: "City name is required" }); } try { - // 2️⃣ Fetch data from OpenWeather + // Call OpenWeather API with metric units const response = await fetch( `https://api.openweathermap.org/data/2.5/weather?q=${cityName}&appid=${keys.API_KEY}&units=metric` ); const data = await response.json(); - // 3️⃣ Handle city not found + // If city is not found (API may return cod as string or message) if (data.cod === "404" || data.message === "city not found") { return res.status(404).json({ weatherText: "City is not found!" }); } - // 4️⃣ Send temperature + // Success: respond with temperature in °C const temperature = data.main.temp; res.json({ weatherText: `The temperature in ${cityName} is ${temperature}°C` }); } catch (error) { + // Generic server error (e.g., network issues) console.error(error); res.status(500).json({ weatherText: "Something went wrong!" }); }