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..f491289fe --- /dev/null +++ b/assignments/HackYourTemperature/app.js @@ -0,0 +1,61 @@ +// app.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()); + +// 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 (Week 2): Accepts JSON body { cityName } and returns weather text +app.post("/weather", async (req, res) => { + const { cityName } = req.body; + + // Validate input: cityName is required + if (!cityName) { + return res.status(400).json({ weatherText: "City name is required" }); + } + + try { + // 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(); + + // 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!" }); + } + + // 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!" }); + } +}); + +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 new file mode 100644 index 000000000..194537672 --- /dev/null +++ b/assignments/HackYourTemperature/package.json @@ -0,0 +1,27 @@ +{ + "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", + "test": "jest" +}, + "keywords": [], + "author": "Majd Jad_alhaq", + "license": "ISC", + "dependencies": { + "express": "^4.21.1", + "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.7", + "supertest": "^7.1.4" + } +} diff --git a/assignments/HackYourTemperature/server.js b/assignments/HackYourTemperature/server.js new file mode 100644 index 000000000..ff52bffa1 --- /dev/null +++ b/assignments/HackYourTemperature/server.js @@ -0,0 +1,7 @@ +// server.js +import app from "./app.js"; + +const PORT = 3000; +app.listen(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 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'); +}); 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"); +});