Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 35 additions & 0 deletions assignments/HackYourTemperature/__tests__/app.test.js
Original file line number Diff line number Diff line change
@@ -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");
});
});
61 changes: 61 additions & 0 deletions assignments/HackYourTemperature/app.js
Original file line number Diff line number Diff line change
@@ -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;
4 changes: 4 additions & 0 deletions assignments/HackYourTemperature/babel.config.cjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
// babel.config.cjs
module.exports = {
presets: [["@babel/preset-env", { targets: { node: "current" } }]]
};
12 changes: 12 additions & 0 deletions assignments/HackYourTemperature/jest.config.js
Original file line number Diff line number Diff line change
@@ -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)/)"
]
};

27 changes: 27 additions & 0 deletions assignments/HackYourTemperature/package.json
Original file line number Diff line number Diff line change
@@ -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"
}
}
7 changes: 7 additions & 0 deletions assignments/HackYourTemperature/server.js
Original file line number Diff line number Diff line change
@@ -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}`);
});
7 changes: 7 additions & 0 deletions assignments/HackYourTemperature/sources/keys.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
// sources/keys.js
const keys = {
API_KEY: "fe2fd877137b6c61f6314ea9c69e8460"
};

export default keys;

41 changes: 35 additions & 6 deletions week1/prep-exercises/1-web-server/server.js
Original file line number Diff line number Diff line change
Expand Up @@ -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');
});
1 change: 1 addition & 0 deletions week2/prep-exercises/1-blog-API/blogs/My first blog
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Lorem ipsum
2 changes: 1 addition & 1 deletion week2/prep-exercises/1-blog-API/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,6 @@
"author": "",
"license": "ISC",
"dependencies": {
"express": "^4.17.1"
"express": "^4.21.2"
}
}
101 changes: 92 additions & 9 deletions week2/prep-exercises/1-blog-API/server.js
Original file line number Diff line number Diff line change
@@ -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)
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");
});