diff --git a/assignments/hackyourtemperature/.env b/assignments/hackyourtemperature/.env new file mode 100644 index 000000000..c0c68b1ca --- /dev/null +++ b/assignments/hackyourtemperature/.env @@ -0,0 +1 @@ +PORT=3000 \ No newline at end of file diff --git a/assignments/hackyourtemperature/.gitignore b/assignments/hackyourtemperature/.gitignore new file mode 100644 index 000000000..9a3d3c991 --- /dev/null +++ b/assignments/hackyourtemperature/.gitignore @@ -0,0 +1,4 @@ +node_modules/ +.env +.vscode +npm-debug.log \ 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..e39456279 --- /dev/null +++ b/assignments/hackyourtemperature/__tests__/app.test.js @@ -0,0 +1,62 @@ +import app from "../src/app.js"; +import supertest from "supertest"; + +const request = supertest(app); + +describe("GET /", () => { + it("Should return text 'hello from backend to frontend'", async () => { + const response = await request.get("/"); + + expect(response.text).toBe("hello from backend to frontend!"); + expect(response.status).toBe(200); + }); +}); + +describe("POST /api/weather", () => { + it("Should response JSON with cityName and temperature properties", async () => { + const response = await request.post("/api/weather").send({ + cityName: "Amsterdam", + }); + + expect(response.status).toBe(200); + expect(response.body).toHaveProperty("cityName"); + expect(response.body).toHaveProperty("temperature"); + }); + + it("Should response JSON with cityName not found", async () => { + const cityName = "andromeda"; + const response = await request.post("/api/weather").send({ + cityName: `${cityName}`, + }); + expect(response.status).toBe(404); + expect(response.body).toEqual({ error: `${cityName} not found` }); + }); + + it("Should response 'Request body is missing'", async () => { + const response = await request.post("/api/weather"); + + expect(response.status).toBe(400); + expect(response.body.error).toMatch(/request body is missing/i); + }); + + it("Should response 'Required parameter 'cityName' is missing'", async () => { + const response = await request + .post("/api/weather") + .send({ cityName: " " }); + + expect(response.status).toBe(400); + expect(response.body).toEqual({ + error: "Required parameter 'cityName' is missing", + }); + }); +}); + +describe("Unknown routes", () => { + it("Should respond with 404 for undefined path", async () => { + const response = await request.get("/non-existing-path"); + + expect(response.status).toBe(404); + expect(response.body).toHaveProperty("error"); + expect(response.body.error).toMatch(/not found/i); + }); +}); diff --git a/assignments/config-files/babel.config.cjs b/assignments/hackyourtemperature/babel.config.cjs similarity index 64% rename from assignments/config-files/babel.config.cjs rename to assignments/hackyourtemperature/babel.config.cjs index fbb629af6..392abb66d 100644 --- a/assignments/config-files/babel.config.cjs +++ b/assignments/hackyourtemperature/babel.config.cjs @@ -1,7 +1,6 @@ module.exports = { presets: [ [ - // This is a configuration, here we are telling babel what configuration to use "@babel/preset-env", { targets: { diff --git a/assignments/config-files/jest.config.js b/assignments/hackyourtemperature/jest.config.js similarity index 100% rename from assignments/config-files/jest.config.js rename to assignments/hackyourtemperature/jest.config.js diff --git a/assignments/hackyourtemperature/package.json b/assignments/hackyourtemperature/package.json new file mode 100644 index 000000000..696127971 --- /dev/null +++ b/assignments/hackyourtemperature/package.json @@ -0,0 +1,23 @@ +{ + "name": "hackyourtemperature", + "version": "1.0.0", + "description": "", + "main": "server.js", + "type": "module", + "scripts": { + "start": "node src/server.js", + "dev": "node --watch --env-file=.env src/server.js", + "test": "jest" + }, + "author": "yanaesher", + "license": "ISC", + "dependencies": { + "express": "^5.1.0" + }, + "devDependencies": { + "@babel/preset-env": "^7.27.1", + "babel-jest": "^29.7.0", + "jest": "^29.7.0", + "supertest": "^7.1.0" + } +} diff --git a/assignments/hackyourtemperature/src/app.js b/assignments/hackyourtemperature/src/app.js new file mode 100644 index 000000000..40607eb2c --- /dev/null +++ b/assignments/hackyourtemperature/src/app.js @@ -0,0 +1,20 @@ +import express from "express"; +import weatherRouter from "./routes/temperature.route.js"; + +import { notFound } from "./middlewares/notFound.middleware.js"; +import { errorHandler } from "./middlewares/error.middleware.js"; + +const app = express(); + +app.use(express.json()); +app.use(express.urlencoded({ extended: false })); + +app.use("/api/weather", weatherRouter); +app.get("/api", (req, res) => { + res.status(200).send("hello from backend to frontend!"); +}); + +app.use(notFound); +app.use(errorHandler); + +export default app; diff --git a/assignments/hackyourtemperature/src/constants.js b/assignments/hackyourtemperature/src/constants.js new file mode 100644 index 000000000..78fbf616e --- /dev/null +++ b/assignments/hackyourtemperature/src/constants.js @@ -0,0 +1,2 @@ +export const BASE_WEATHER_URL = + "https://api.openweathermap.org/data/2.5/weather"; diff --git a/assignments/hackyourtemperature/src/controllers/weather.controller.js b/assignments/hackyourtemperature/src/controllers/weather.controller.js new file mode 100644 index 000000000..a22188f28 --- /dev/null +++ b/assignments/hackyourtemperature/src/controllers/weather.controller.js @@ -0,0 +1,25 @@ +import { fetchCurrentWeather } from "../services/weather.service.js"; + +export async function getCityCurrentWeather(req, res, next) { + const { cityName } = req.body; + + if (!cityName?.trim()) { + const error = new Error("Required parameter 'cityName' is missing"); + error.status = 400; + + return next(error); + } + + try { + const { name, main } = await fetchCurrentWeather(cityName); + res.status(200).json({ cityName: name, temperature: main.temp }); + } catch (err) { + if (err.status === 404) { + const notFoundMessage = new Error(`${cityName} not found`); + notFoundMessage.status = 404; + return next(notFoundMessage); + } + + next(err); + } +} diff --git a/assignments/hackyourtemperature/src/keys.js b/assignments/hackyourtemperature/src/keys.js new file mode 100644 index 000000000..b10003544 --- /dev/null +++ b/assignments/hackyourtemperature/src/keys.js @@ -0,0 +1 @@ +export const API_WEATHER_KEY = "007dd5ca6a8f91773d26bde4f0099937"; diff --git a/assignments/hackyourtemperature/src/middlewares/error.middleware.js b/assignments/hackyourtemperature/src/middlewares/error.middleware.js new file mode 100644 index 000000000..d252b51e2 --- /dev/null +++ b/assignments/hackyourtemperature/src/middlewares/error.middleware.js @@ -0,0 +1,6 @@ +export function errorHandler(err, req, res, next) { + const statusCode = err.status || 500; + const message = err.message || "Something went wrong"; + + res.status(statusCode).json({ error: message }); +} diff --git a/assignments/hackyourtemperature/src/middlewares/notFound.middleware.js b/assignments/hackyourtemperature/src/middlewares/notFound.middleware.js new file mode 100644 index 000000000..46b644b6f --- /dev/null +++ b/assignments/hackyourtemperature/src/middlewares/notFound.middleware.js @@ -0,0 +1,5 @@ +export function notFound(req, res, next) { + const error = new Error(`Not found`); + error.status = 404; + next(error); +} diff --git a/assignments/hackyourtemperature/src/middlewares/validateBody.js b/assignments/hackyourtemperature/src/middlewares/validateBody.js new file mode 100644 index 000000000..823ecc9e9 --- /dev/null +++ b/assignments/hackyourtemperature/src/middlewares/validateBody.js @@ -0,0 +1,9 @@ +export function validateBody(req, res, next) { + if (!req.body) { + const error = new Error("Request body is missing"); + error.status = 400; + return next(error); + } + + next(); +} diff --git a/assignments/hackyourtemperature/src/routes/temperature.route.js b/assignments/hackyourtemperature/src/routes/temperature.route.js new file mode 100644 index 000000000..165d132f2 --- /dev/null +++ b/assignments/hackyourtemperature/src/routes/temperature.route.js @@ -0,0 +1,8 @@ +import { Router } from "express"; +import { validateBody } from "../middlewares/validateBody.js"; +import { getCityCurrentWeather } from "../controllers/weather.controller.js"; +const router = Router(); + +router.post("/", validateBody, getCityCurrentWeather); + +export default router; diff --git a/assignments/hackyourtemperature/src/server.js b/assignments/hackyourtemperature/src/server.js new file mode 100644 index 000000000..7adc49ba2 --- /dev/null +++ b/assignments/hackyourtemperature/src/server.js @@ -0,0 +1,7 @@ +import app from "./app.js"; + +const PORT = process.env.PORT || 3000; + +app.listen(PORT, () => { + console.log(`Server is running at http://127.0.0.1:${PORT}`); +}); diff --git a/assignments/hackyourtemperature/src/services/weather.service.js b/assignments/hackyourtemperature/src/services/weather.service.js new file mode 100644 index 000000000..5a17d8231 --- /dev/null +++ b/assignments/hackyourtemperature/src/services/weather.service.js @@ -0,0 +1,10 @@ +import { fetchData } from "../utils/fetchHelper.js"; +import { API_WEATHER_KEY } from "../keys.js"; +import { BASE_WEATHER_URL } from "../constants.js"; + +export async function fetchCurrentWeather(cityName) { + const url = `${BASE_WEATHER_URL}?q=${cityName}&appid=${API_WEATHER_KEY}&units=metric`; + + const weatherData = await fetchData(url); + return weatherData; +} diff --git a/assignments/hackyourtemperature/src/utils/fetchHelper.js b/assignments/hackyourtemperature/src/utils/fetchHelper.js new file mode 100644 index 000000000..64974a855 --- /dev/null +++ b/assignments/hackyourtemperature/src/utils/fetchHelper.js @@ -0,0 +1,11 @@ +export async function fetchData(url) { + const response = await fetch(url); + + if (!response.ok) { + const error = new Error(`Failed to fetch: ${response.status}`); + error.status = response.status; + throw error; + } + + return response.json(); +} diff --git a/week1/practice-exercises/1-pad-numbers/padLeft.js b/week1/practice-exercises/1-pad-numbers/padLeft.js index b58f887a3..2436068e1 100644 --- a/week1/practice-exercises/1-pad-numbers/padLeft.js +++ b/week1/practice-exercises/1-pad-numbers/padLeft.js @@ -4,7 +4,7 @@ * e.g. padLeft('foo', 5, '_') -> '__foo' * e.g. padLeft( '2', 2, '0') -> '02' */ -function padLeft(val, num, str) { +export function padLeft(val, num, str) { return '00000'.replace(/0/g, str).slice(0, num - val.length) + val; } diff --git a/week1/practice-exercises/1-pad-numbers/script.js b/week1/practice-exercises/1-pad-numbers/script.js index c11264e39..c0d907c46 100644 --- a/week1/practice-exercises/1-pad-numbers/script.js +++ b/week1/practice-exercises/1-pad-numbers/script.js @@ -1,21 +1,22 @@ - +import { padLeft } from "./padLeft.js"; /** - ** Exercise 1: Pad numbers - * + * + * + ** Exercise 1: Pad numbers + * * In this file use the padLeft function from padLeft.js to * pad the numbers to exactly 5 spaces and log them to the console - * + * * Expected output (replace the underscore with spaces): - * + * * ___12; * __846; * ____2; * _1236; - * + * * Tips: * where to use `exports` and where `require`? */ -let numbers = [ "12", "846", "2", "1236" ]; - -// YOUR CODE GOES HERE +let numbers = ["12", "846", "2", "1236"]; +numbers.forEach((num) => console.log(padLeft(num, 5, "_"))); diff --git a/week1/practice-exercises/2-left-pad/padLeft.js b/week1/practice-exercises/2-left-pad/padLeft.js index b58f887a3..0b7c83b8d 100644 --- a/week1/practice-exercises/2-left-pad/padLeft.js +++ b/week1/practice-exercises/2-left-pad/padLeft.js @@ -7,5 +7,3 @@ function padLeft(val, num, str) { return '00000'.replace(/0/g, str).slice(0, num - val.length) + val; } - -// YOUR CODE GOES HERE \ No newline at end of file diff --git a/week1/practice-exercises/2-left-pad/script.js b/week1/practice-exercises/2-left-pad/script.js index e0e081243..49f50a6b9 100644 --- a/week1/practice-exercises/2-left-pad/script.js +++ b/week1/practice-exercises/2-left-pad/script.js @@ -1,13 +1,5 @@ -/** - ** Exercise 2: To the left, to the left... - * - * Copy and paste your code from the previous exercise. - * Replace the function `padLeft` to use - * this new NPM package called `left-pad` instead then - * Pad the numbers to 8 characters to confirm that it works correctly - * - */ +import padLeft from "left-pad"; -let numbers = [ "12", "846", "2", "1236" ]; +let numbers = ["12", "846", "2", "1236"]; -// YOUR CODE GOES HERE \ No newline at end of file +numbers.forEach((num) => console.log(padLeft(num, 8, "_"))); diff --git a/week1/practice-exercises/package.json b/week1/practice-exercises/package.json new file mode 100644 index 000000000..b06fbd92c --- /dev/null +++ b/week1/practice-exercises/package.json @@ -0,0 +1,16 @@ +{ + "name": "practice-exercises", + "version": "1.0.0", + "description": "", + "main": "index.js", + "type": "module", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "keywords": [], + "author": "", + "license": "ISC", + "devDependencies": { + "left-pad": "^1.3.0" + } +} diff --git a/week1/prep-exercises/1-web-server/index.html b/week1/prep-exercises/1-web-server/index.html index c64f7dcfa..0b326f19d 100644 --- a/week1/prep-exercises/1-web-server/index.html +++ b/week1/prep-exercises/1-web-server/index.html @@ -1,5 +1,6 @@
+